diff --git a/.circleci/config.yml b/.circleci/config.yml
index d688fc738005..6b083ce6478a 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -75,10 +75,9 @@ jobs:
- run: mix compile
- # Ensure NIF is compiled for libsecp256k1
- run:
command: make
- working_directory: "deps/libsecp256k1"
+ working_directory: "deps/ex_secp256k1"
# `deps` needs to be cached with `_build` because `_build` will symlink into `deps`
diff --git a/.dialyzer-ignore b/.dialyzer-ignore
index 326c4a9a0835..15662b23302e 100644
--- a/.dialyzer-ignore
+++ b/.dialyzer-ignore
@@ -3,15 +3,24 @@
:0: Unknown type 'Elixir.Map':t/0
:0: Unknown type 'Elixir.Hash':t/0
:0: Unknown type 'Elixir.Address':t/0
-lib/ethereum_jsonrpc/rolling_window.ex:173
+lib/ethereum_jsonrpc/rolling_window.ex:171
lib/explorer/smart_contract/solidity/publisher_worker.ex:1
lib/explorer/smart_contract/vyper/publisher_worker.ex:1
-lib/explorer/smart_contract/solidity/publisher_worker.ex:6
-lib/explorer/smart_contract/vyper/publisher_worker.ex:6
+lib/explorer/smart_contract/solidity/publisher_worker.ex:8
+lib/explorer/smart_contract/vyper/publisher_worker.ex:8
lib/block_scout_web/router.ex:1
lib/block_scout_web/schema/types.ex:31
lib/phoenix/router.ex:324
lib/phoenix/router.ex:402
lib/explorer/smart_contract/reader.ex:435
-lib/explorer/exchange_rates/source.ex:123
-lib/explorer/exchange_rates/source.ex:126
+lib/indexer/fetcher/polygon_edge.ex:737
+lib/indexer/fetcher/polygon_edge/deposit_execute.ex:151
+lib/indexer/fetcher/polygon_edge/deposit_execute.ex:195
+lib/indexer/fetcher/polygon_edge/withdrawal.ex:166
+lib/indexer/fetcher/polygon_edge/withdrawal.ex:210
+lib/indexer/fetcher/zkevm/transaction_batch.ex:116
+lib/indexer/fetcher/zkevm/transaction_batch.ex:156
+lib/indexer/fetcher/zkevm/transaction_batch.ex:252
+lib/block_scout_web/views/api/v2/transaction_view.ex:431
+lib/block_scout_web/views/api/v2/transaction_view.ex:472
+lib/explorer/chain/transaction.ex:170
diff --git a/.dockerignore b/.dockerignore
index b52edd4f4769..8ff38c4786c6 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -5,5 +5,8 @@ apps/explorer/node_modules
test
.git
.circleci
+.vscode
+.elixir_ls
+erl_crash.dump
logs
apps/*/test
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 000000000000..dcd9fc29d2b7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,100 @@
+name: Bug Report
+description: File a bug report
+labels: [ "triage" ]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for reporting a bug 🐛!
+
+ Please search open/closed issues before submitting. Someone might have had the similar problem before 😉!
+
+ - type: textarea
+ id: description
+ attributes:
+ label: Description
+ description: A brief description of the issue.
+ validations:
+ required: true
+
+ - type: dropdown
+ id: installation-type
+ attributes:
+ label: Type of the installation
+ description: How the application has been deployed.
+ options:
+ - Docker-compose
+ - Helm charts (k8s)
+ - Manual from the source code
+ - Docker
+ validations:
+ required: true
+
+ - type: input
+ id: archive-node-type
+ attributes:
+ label: Type of the JSON RPC archive node
+ description: Which type of archive node is used.
+ placeholder: "Erigon/Geth/Nethermind/Reth/PolygonEdge/Besu/OpenEthereum/..."
+ validations:
+ required: true
+
+ - type: input
+ id: chain-type
+ attributes:
+ label: Type of the chain
+ description: Type of the chain.
+ placeholder: L1/L2/...
+
+ - type: input
+ id: link
+ attributes:
+ label: Link to the page
+ description: The link to the page where the issue occurs.
+ placeholder: https://eth.blockscout.com
+
+ - type: textarea
+ id: steps
+ attributes:
+ label: Steps to reproduce
+ description: |
+ Explain how to reproduce the issue in the development environment.
+
+ - type: input
+ id: backend-version
+ attributes:
+ label: Backend version
+ description: The release version of the backend or branch/commit.
+ placeholder: v6.0.0
+ validations:
+ required: true
+
+ - type: input
+ id: frontend-version
+ attributes:
+ label: Frontend version
+ description: The release version of the frontend or branch/commit.
+ placeholder: v1.11.1
+
+ - type: input
+ id: elixir-version
+ attributes:
+ label: Elixir & Erlang/OTP versions
+ description: Elixir & Erlang/OTP versions.
+ placeholder: Elixir 1.14.5 (compiled with Erlang/OTP 25)
+ validations:
+ required: true
+
+ - type: input
+ id: os-version
+ attributes:
+ label: Operating system
+ description: The operating system this issue occurred with.
+ placeholder: Linux/macOS/Windows
+
+ - type: textarea
+ id: additional-information
+ attributes:
+ label: Additional information
+ description: |
+ Use this section to provide any additional information you might have (e.g screenshots or screencasts).
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000000..439bca677db3
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,11 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Feature Request
+ url: https://blockscout.canny.io/feature-requests
+ about: Request a feature or enhancement
+ - name: Ask a question
+ url: https://github.com/orgs/blockscout/discussions
+ about: Ask questions and discuss topics with other community members
+ - name: Join our Discord Server
+ url: https://discord.gg/blockscout
+ about: The official Blockscout Discord community
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index b6a9ec46bde4..b5eaa004db36 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -4,16 +4,20 @@ updates:
directory: "/"
open-pull-requests-limit: 20
schedule:
- interval: "daily"
+ interval: "weekly"
- package-ecosystem: "npm"
directory: "/apps/block_scout_web/assets"
open-pull-requests-limit: 10
schedule:
- interval: "daily"
+ interval: "weekly"
+ ignore:
+ - dependency-name: "bootstrap"
+ - dependency-name: "web3"
+ versions: ["4.x"]
- package-ecosystem: "npm"
directory: "/apps/explorer"
open-pull-requests-limit: 10
schedule:
- interval: "daily"
+ interval: "weekly"
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 9b8a5cf42ab2..8a4cf36f5bb4 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -38,7 +38,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml
index dd6f455803f8..2e71299d83c1 100644
--- a/.github/workflows/config.yml
+++ b/.github/workflows/config.yml
@@ -4,22 +4,59 @@ on:
push:
branches:
- master
+ - v6.0.0-dev
+ - production-core
+ - production-eth-experimental
+ - production-eth-goerli
+ - production-eth-sepolia
+ - production-fuse
+ - production-optimism
+ - production-immutable
+ - production-iota
+ - production-lukso
+ - production-rsk
+ - production-sokol
+ - production-suave
+ - production-xdai
+ - production-zkevm
+ - production-zksync
+ - staging-l2
+ paths-ignore:
+ - 'CHANGELOG.md'
+ - '**/README.md'
+ - 'docker/*'
+ - 'docker-compose/*'
pull_request:
branches:
- master
+ - v6.0.0-dev
+ - production-optimism
+ - production-zksync
env:
MIX_ENV: test
- OTP_VERSION: '25.2.1'
- ELIXIR_VERSION: '1.14.3'
- ACCOUNT_AUTH0_DOMAIN: 'blockscoutcom.us.auth0.com'
+ OTP_VERSION: "25.2.1"
+ ELIXIR_VERSION: "1.14.5"
+ ACCOUNT_AUTH0_DOMAIN: "blockscoutcom.us.auth0.com"
jobs:
+ matrix-builder:
+ name: Build matrix
+ runs-on: ubuntu-latest
+ outputs:
+ matrix: ${{ steps.set-matrix.outputs.matrix }}
+ steps:
+ - id: set-matrix
+ run: |
+ echo "matrix=$matrixStringifiedObject" >> $GITHUB_OUTPUT
+ env:
+ matrixStringifiedObject: '{"chain-type": ["ethereum", "polygon_edge", "polygon_zkevm", "rsk", "suave", "stability"]}'
+
build-and-cache:
name: Build and Cache deps
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ env.OTP_VERSION }}
@@ -38,7 +75,7 @@ jobs:
path: |
deps
_build
- key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_18-${{ hashFiles('mix.lock') }}
+ key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-
@@ -49,8 +86,6 @@ jobs:
mix local.rebar --force
mix deps.get
mix deps.compile
- cd deps/libsecp256k1
- make
- name: Restore Explorer NPM Cache
uses: actions/cache@v2
@@ -85,7 +120,7 @@ jobs:
runs-on: ubuntu-latest
needs: build-and-cache
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ env.OTP_VERSION }}
@@ -98,7 +133,7 @@ jobs:
path: |
deps
_build
- key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_18-${{ hashFiles('mix.lock') }}
+ key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@@ -109,7 +144,7 @@ jobs:
runs-on: ubuntu-latest
needs: build-and-cache
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ env.OTP_VERSION }}
@@ -122,17 +157,23 @@ jobs:
path: |
deps
_build
- key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_18-${{ hashFiles('mix.lock') }}
+ key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
- run: mix format --check-formatted
+
dialyzer:
+ strategy:
+ fail-fast: false
+ matrix: ${{ fromJson(needs.matrix-builder.outputs.matrix) }}
name: Dialyzer static analysis
runs-on: ubuntu-latest
- needs: build-and-cache
+ needs:
+ - build-and-cache
+ - matrix-builder
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ env.OTP_VERSION }}
@@ -145,7 +186,7 @@ jobs:
path: |
deps
_build
- key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_18-${{ hashFiles('mix.lock') }}
+ key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@@ -154,25 +195,29 @@ jobs:
id: dialyzer-cache
with:
path: priv/plts
- key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-mixlockhash_18-${{ hashFiles('mix.lock') }}
+ key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-${{ matrix.chain-type }}-dialyzer-mixlockhash_25-${{ hashFiles('mix.lock') }}
restore-keys: |
- ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-"
+ ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-${{ matrix.chain-type }}-dialyzer-"
- name: Conditionally build Dialyzer Cache
if: steps.dialyzer-cache.output.cache-hit != 'true'
run: |
mkdir -p priv/plts
mix dialyzer --plt
+ env:
+ CHAIN_TYPE: ${{ matrix.chain-type }}
- name: Run Dialyzer
run: mix dialyzer --halt-exit-status
+ env:
+ CHAIN_TYPE: ${{ matrix.chain-type }}
gettext:
name: Missing translation keys check
runs-on: ubuntu-latest
needs: build-and-cache
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ env.OTP_VERSION }}
@@ -185,7 +230,7 @@ jobs:
path: |
deps
_build
- key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_18-${{ hashFiles('mix.lock') }}
+ key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@@ -198,7 +243,7 @@ jobs:
runs-on: ubuntu-latest
needs: build-and-cache
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ env.OTP_VERSION }}
@@ -211,7 +256,7 @@ jobs:
path: |
deps
_build
- key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_18-${{ hashFiles('mix.lock') }}
+ key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@@ -227,7 +272,7 @@ jobs:
runs-on: ubuntu-latest
needs: build-and-cache
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ env.OTP_VERSION }}
@@ -240,7 +285,7 @@ jobs:
path: |
deps
_build
- key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_18-${{ hashFiles('mix.lock') }}
+ key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@@ -275,7 +320,7 @@ jobs:
runs-on: ubuntu-latest
needs: build-and-cache
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ env.OTP_VERSION }}
@@ -288,7 +333,7 @@ jobs:
path: |
deps
_build
- key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_18-${{ hashFiles('mix.lock') }}
+ key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@@ -321,7 +366,7 @@ jobs:
runs-on: ubuntu-latest
needs: build-and-cache
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ env.OTP_VERSION }}
@@ -334,7 +379,7 @@ jobs:
path: |
deps
_build
- key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_18-${{ hashFiles('mix.lock') }}
+ key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@@ -355,9 +400,14 @@ jobs:
working-directory: apps/block_scout_web/assets
test_nethermind_mox_ethereum_jsonrpc:
+ strategy:
+ fail-fast: false
+ matrix: ${{ fromJson(needs.matrix-builder.outputs.matrix) }}
name: EthereumJSONRPC Tests
runs-on: ubuntu-latest
- needs: build-and-cache
+ needs:
+ - build-and-cache
+ - matrix-builder
services:
postgres:
image: postgres
@@ -378,7 +428,7 @@ jobs:
# Maps tcp port 5432 on service container to the host
- 5432:5432
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ env.OTP_VERSION }}
@@ -391,7 +441,7 @@ jobs:
path: |
deps
_build
- key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_18-${{ hashFiles('mix.lock') }}
+ key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@@ -408,10 +458,16 @@ jobs:
PGUSER: postgres
ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Nethermind.Mox"
ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox"
+ CHAIN_TYPE: "${{ matrix.chain-type }}"
test_nethermind_mox_explorer:
+ strategy:
+ fail-fast: false
+ matrix: ${{ fromJson(needs.matrix-builder.outputs.matrix) }}
name: Explorer Tests
runs-on: ubuntu-latest
- needs: build-and-cache
+ needs:
+ - build-and-cache
+ - matrix-builder
services:
postgres:
image: postgres
@@ -432,7 +488,7 @@ jobs:
# Maps tcp port 5432 on service container to the host
- 5432:5432
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ env.OTP_VERSION }}
@@ -445,7 +501,7 @@ jobs:
path: |
deps
_build
- key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_18-${{ hashFiles('mix.lock') }}
+ key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@@ -473,10 +529,16 @@ jobs:
PGUSER: postgres
ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Nethermind.Mox"
ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox"
+ CHAIN_TYPE: "${{ matrix.chain-type }}"
test_nethermind_mox_indexer:
+ strategy:
+ fail-fast: false
+ matrix: ${{ fromJson(needs.matrix-builder.outputs.matrix) }}
name: Indexer Tests
runs-on: ubuntu-latest
- needs: build-and-cache
+ needs:
+ - build-and-cache
+ - matrix-builder
services:
postgres:
image: postgres
@@ -497,7 +559,7 @@ jobs:
# Maps tcp port 5432 on service container to the host
- 5432:5432
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ env.OTP_VERSION }}
@@ -510,11 +572,10 @@ jobs:
path: |
deps
_build
- key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_18-${{ hashFiles('mix.lock') }}
+ key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
-
- run: ./bin/install_chrome_headless.sh
- name: mix test --exclude no_nethermind
@@ -531,15 +592,20 @@ jobs:
PGUSER: postgres
ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Nethermind.Mox"
ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox"
-
+ CHAIN_TYPE: "${{ matrix.chain-type }}"
test_nethermind_mox_block_scout_web:
+ strategy:
+ fail-fast: false
+ matrix: ${{ fromJson(needs.matrix-builder.outputs.matrix) }}
name: Blockscout Web Tests
runs-on: ubuntu-latest
- needs: build-and-cache
+ needs:
+ - build-and-cache
+ - matrix-builder
services:
redis_db:
- image: 'redis:alpine'
- ports:
+ image: "redis:alpine"
+ ports:
- 6379:6379
postgres:
@@ -561,7 +627,7 @@ jobs:
# Maps tcp port 5432 on service container to the host
- 5432:5432
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ env.OTP_VERSION }}
@@ -574,11 +640,10 @@ jobs:
path: |
deps
_build
- key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_18-${{ hashFiles('mix.lock') }}
+ key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
-
- name: Restore Explorer NPM Cache
uses: actions/cache@v2
id: explorer-npm-cache
@@ -617,10 +682,10 @@ jobs:
PGUSER: postgres
ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Nethermind.Mox"
ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox"
- CHAIN_ID: "77"
+ CHAIN_ID: "10200"
API_RATE_LIMIT_DISABLED: "true"
ADMIN_PANEL_ENABLED: "true"
ACCOUNT_ENABLED: "true"
ACCOUNT_REDIS_URL: "redis://localhost:6379"
- API_V2_ENABLED: "true"
SOURCIFY_INTEGRATION_ENABLED: "true"
+ CHAIN_TYPE: "${{ matrix.chain-type }}"
diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml
new file mode 100644
index 000000000000..2d09956875c9
--- /dev/null
+++ b/.github/workflows/prerelease.yml
@@ -0,0 +1,65 @@
+name: Pre-release master
+
+on:
+ workflow_dispatch:
+ inputs:
+ number:
+ type: number
+ required: true
+
+env:
+ OTP_VERSION: '25.2.1'
+ ELIXIR_VERSION: '1.14.5'
+
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: blockscout/blockscout
+
+ - name: Build & Push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ cache-from: type=registry,ref=blockscout/blockscout:buildcache
+ cache-to: type=registry,ref=blockscout/blockscout:buildcache,mode=max
+ tags: blockscout/blockscout:latest, blockscout/blockscout:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}
+ platforms: |
+ linux/amd64
+ linux/arm64/v8
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ DECODE_NOT_A_CONTRACT_CALLS=false
+ MIXPANEL_URL=
+ MIXPANEL_TOKEN=
+ AMPLITUDE_URL=
+ AMPLITUDE_API_KEY=
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
\ No newline at end of file
diff --git a/.github/workflows/publish-docker-image-every-push.yml b/.github/workflows/publish-docker-image-every-push.yml
index 94b8727136f7..25d9c3fa9960 100644
--- a/.github/workflows/publish-docker-image-every-push.yml
+++ b/.github/workflows/publish-docker-image-every-push.yml
@@ -4,10 +4,14 @@ on:
push:
branches:
- master
+ paths-ignore:
+ - 'CHANGELOG.md'
+ - '**/README.md'
+ - 'docker-compose/*'
env:
OTP_VERSION: '25.2.1'
- ELIXIR_VERSION: '1.14.3'
- RELEASE_VERSION: 5.1.5
+ ELIXIR_VERSION: '1.14.5'
+ RELEASE_VERSION: 6.0.0
jobs:
push_to_registry:
@@ -18,23 +22,20 @@ jobs:
short-sha: ${{ steps.output-step.outputs.short-sha }}
steps:
- name: Check out the repo
- uses: actions/checkout@v3
-
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v2
+ uses: actions/checkout@v4
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v2
+ uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
- uses: docker/login-action@v2
+ uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker
id: meta
- uses: docker/metadata-action@v4
+ uses: docker/metadata-action@v5
with:
images: blockscout/blockscout
@@ -48,7 +49,7 @@ jobs:
id: output-step
- name: Build and push Docker image
- uses: docker/build-push-action@v3
+ uses: docker/build-push-action@v5
with:
context: .
file: ./docker/Dockerfile
@@ -73,7 +74,7 @@ jobs:
RELEASE_VERSION=${{ env.RELEASE_VERSION }}
- name: Build and push Docker image for frontend
- uses: docker/build-push-action@v3
+ uses: docker/build-push-action@v5
with:
context: .
file: ./docker/Dockerfile
@@ -91,13 +92,34 @@ jobs:
SESSION_COOKIE_DOMAIN=k8s-dev.blockscout.com
BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }}
RELEASE_VERSION=${{ env.RELEASE_VERSION }}
- tests:
+ deploy_e2e:
needs: push_to_registry
+ runs-on: ubuntu-latest
+ permissions: write-all
+ steps:
+ - name: Get Vault credentials
+ id: retrieve-vault-secrets
+ uses: hashicorp/vault-action@v2.4.1
+ with:
+ url: https://vault.k8s.blockscout.com
+ role: ci-dev
+ path: github-jwt
+ method: jwt
+ tlsSkipVerify: false
+ exportToken: true
+ secrets: |
+ ci/data/dev/github token | WORKFLOW_TRIGGER_TOKEN ;
+ - name: Trigger deploy
+ uses: convictional/trigger-workflow-and-wait@v1.6.1
+ with:
+ owner: blockscout
+ repo: deployment-values
+ github_token: ${{env.WORKFLOW_TRIGGER_TOKEN}}
+ workflow_file_name: deploy_blockscout.yaml
+ ref: main
+ wait_interval: 30
+ client_payload: '{ "instance": "dev", "globalEnv": "e2e"}'
+ test:
+ needs: deploy_e2e
uses: blockscout/blockscout-ci-cd/.github/workflows/e2e_new.yaml@master
- with:
- blockscoutImage: blockscout/blockscout:${{ needs.push_to_registry.outputs.release-version }}-prerelease-${{ needs.push_to_registry.outputs.short-sha }}
- blockscoutIngressHost: e2e-blockscout-$GITHUB_SHA_SHORT
- frontendIngressHost: e2e-blockscout-$GITHUB_SHA_SHORT
- gethIngressHost: e2e-geth-$GITHUB_SHA_SHORT
- scVerifierIngressHost: e2e-sc-verifier-$GITHUB_SHA_SHORT
secrets: inherit
diff --git a/.github/workflows/publish-docker-image-for-core.yml b/.github/workflows/publish-docker-image-for-core.yml
new file mode 100644
index 000000000000..61cb6f8189a2
--- /dev/null
+++ b/.github/workflows/publish-docker-image-for-core.yml
@@ -0,0 +1,55 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: POA Core Publish Docker image
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - production-core
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ DOCKER_CHAIN_NAME: poa
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: blockscout/blockscout
+
+ - name: Add SHORT_SHA env property with commit short sha
+ run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:latest, blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }}
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
\ No newline at end of file
diff --git a/.github/workflows/publish-docker-image-for-eth-goerli.yml b/.github/workflows/publish-docker-image-for-eth-goerli.yml
new file mode 100644
index 000000000000..f381283cf359
--- /dev/null
+++ b/.github/workflows/publish-docker-image-for-eth-goerli.yml
@@ -0,0 +1,55 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: ETH Goerli Publish Docker image
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - production-eth-goerli
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ DOCKER_CHAIN_NAME: eth-goerli
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: blockscout/blockscout
+
+ - name: Add SHORT_SHA env property with commit short sha
+ run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:latest, blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }}
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
\ No newline at end of file
diff --git a/.github/workflows/publish-docker-image-for-eth-sepolia.yml b/.github/workflows/publish-docker-image-for-eth-sepolia.yml
new file mode 100644
index 000000000000..b24c2571676e
--- /dev/null
+++ b/.github/workflows/publish-docker-image-for-eth-sepolia.yml
@@ -0,0 +1,55 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: ETH Sepolia Publish Docker image
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - production-eth-sepolia
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ DOCKER_CHAIN_NAME: eth-sepolia
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: blockscout/blockscout
+
+ - name: Add SHORT_SHA env property with commit short sha
+ run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:latest, blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }}
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
\ No newline at end of file
diff --git a/.github/workflows/publish-docker-image-for-eth.yml b/.github/workflows/publish-docker-image-for-eth.yml
new file mode 100644
index 000000000000..a663380306f2
--- /dev/null
+++ b/.github/workflows/publish-docker-image-for-eth.yml
@@ -0,0 +1,55 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: ETH Publish Docker image
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - production-eth-experimental
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ DOCKER_CHAIN_NAME: mainnet
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: blockscout/blockscout
+
+ - name: Add SHORT_SHA env property with commit short sha
+ run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-experimental
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=false
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }}
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
\ No newline at end of file
diff --git a/.github/workflows/publish-docker-image-for-filecoin.yml b/.github/workflows/publish-docker-image-for-filecoin.yml
new file mode 100644
index 000000000000..6e98141461fc
--- /dev/null
+++ b/.github/workflows/publish-docker-image-for-filecoin.yml
@@ -0,0 +1,55 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: Publish Docker image for specific chain branches
+
+on:
+ push:
+ branches:
+ - production-filecoin
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ DOCKER_CHAIN_NAME: filecoin
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v3
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v4
+ with:
+ images: blockscout/blockscout
+
+ - name: Add SHORT_SHA env property with commit short sha
+ run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }}
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
+ CHAIN_TYPE=polygon_edge
\ No newline at end of file
diff --git a/.github/workflows/publish-docker-image-for-fuse.yml b/.github/workflows/publish-docker-image-for-fuse.yml
new file mode 100644
index 000000000000..a9ec8713e782
--- /dev/null
+++ b/.github/workflows/publish-docker-image-for-fuse.yml
@@ -0,0 +1,58 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: Fuse Publish Docker image
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - production-fuse
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ DOCKER_CHAIN_NAME: fuse
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: blockscout/blockscout
+
+ - name: Add SHORT_SHA env property with commit short sha
+ run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:latest, blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }}
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
\ No newline at end of file
diff --git a/.github/workflows/publish-docker-image-for-immutable.yml b/.github/workflows/publish-docker-image-for-immutable.yml
new file mode 100644
index 000000000000..8bd44e3d1ccd
--- /dev/null
+++ b/.github/workflows/publish-docker-image-for-immutable.yml
@@ -0,0 +1,62 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: Immutable Publish Docker image
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - production-immutable
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ DOCKER_CHAIN_NAME: immutable
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: blockscout/blockscout
+
+ - name: Add SHORT_SHA env property with commit short sha
+ run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:latest, blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}
+ platforms: |
+ linux/amd64
+ linux/arm64/v8
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }}
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
+ CHAIN_TYPE=polygon_edge
\ No newline at end of file
diff --git a/.github/workflows/publish-docker-image-for-l2-staging.yml b/.github/workflows/publish-docker-image-for-l2-staging.yml
new file mode 100644
index 000000000000..1c5f290907a6
--- /dev/null
+++ b/.github/workflows/publish-docker-image-for-l2-staging.yml
@@ -0,0 +1,55 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: L2 staging Publish Docker image
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - staging-l2
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ DOCKER_CHAIN_NAME: optimism-l2-advanced
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: blockscout/blockscout
+
+ - name: Add SHORT_SHA env property with commit short sha
+ run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:latest, blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }}
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
\ No newline at end of file
diff --git a/.github/workflows/publish-docker-image-for-lukso.yml b/.github/workflows/publish-docker-image-for-lukso.yml
new file mode 100644
index 000000000000..10efc69fe25a
--- /dev/null
+++ b/.github/workflows/publish-docker-image-for-lukso.yml
@@ -0,0 +1,55 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: LUKSO Publish Docker image
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - production-lukso
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ DOCKER_CHAIN_NAME: lukso
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: blockscout/blockscout
+
+ - name: Add SHORT_SHA env property with commit short sha
+ run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:latest, blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }}
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
\ No newline at end of file
diff --git a/.github/workflows/publish-docker-image-for-optimism.yml b/.github/workflows/publish-docker-image-for-optimism.yml
new file mode 100644
index 000000000000..f0c24fa58221
--- /dev/null
+++ b/.github/workflows/publish-docker-image-for-optimism.yml
@@ -0,0 +1,61 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: Optimism Publish Docker image
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - production-optimism
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ DOCKER_CHAIN_NAME: optimism
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: blockscout/blockscout
+
+ - name: Add SHORT_SHA env property with commit short sha
+ run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:latest, blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}
+ platforms: |
+ linux/amd64
+ linux/arm64/v8
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }}
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
\ No newline at end of file
diff --git a/.github/workflows/publish-docker-image-for-polygon-edge.yml b/.github/workflows/publish-docker-image-for-polygon-edge.yml
new file mode 100644
index 000000000000..a7384c4f29fc
--- /dev/null
+++ b/.github/workflows/publish-docker-image-for-polygon-edge.yml
@@ -0,0 +1,56 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: Polygon Edge Publish Docker image
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - production-polygon-edge
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ DOCKER_CHAIN_NAME: polygon-edge
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v3
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v4
+ with:
+ images: blockscout/blockscout
+
+ - name: Add SHORT_SHA env property with commit short sha
+ run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }}
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
+ CHAIN_TYPE=polygon_edge
\ No newline at end of file
diff --git a/.github/workflows/publish-docker-image-for-rsk.yml b/.github/workflows/publish-docker-image-for-rsk.yml
new file mode 100644
index 000000000000..615ad8820bca
--- /dev/null
+++ b/.github/workflows/publish-docker-image-for-rsk.yml
@@ -0,0 +1,56 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: Rootstock Publish Docker image
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - production-rsk
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ DOCKER_CHAIN_NAME: rsk
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: blockscout/blockscout
+
+ - name: Add SHORT_SHA env property with commit short sha
+ run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }}
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
+ CHAIN_TYPE=rsk
\ No newline at end of file
diff --git a/.github/workflows/publish-docker-image-for-stability.yml b/.github/workflows/publish-docker-image-for-stability.yml
new file mode 100644
index 000000000000..c14e5a6a6ac9
--- /dev/null
+++ b/.github/workflows/publish-docker-image-for-stability.yml
@@ -0,0 +1,62 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: Stability Publish Docker image
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - production-stability
+env:
+ OTP_VERSION: '25.2.1'
+ ELIXIR_VERSION: '1.14.5'
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ DOCKER_CHAIN_NAME: stability
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: blockscout/blockscout
+
+ - name: Add SHORT_SHA env property with commit short sha
+ run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }}
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
+ CHAIN_TYPE=stability
\ No newline at end of file
diff --git a/.github/workflows/publish-docker-image-for-suave.yml b/.github/workflows/publish-docker-image-for-suave.yml
new file mode 100644
index 000000000000..0f3c4ad43982
--- /dev/null
+++ b/.github/workflows/publish-docker-image-for-suave.yml
@@ -0,0 +1,65 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: SUAVE Publish Docker image
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - production-suave
+env:
+ OTP_VERSION: '25.2.1'
+ ELIXIR_VERSION: '1.14.5'
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ DOCKER_CHAIN_NAME: suave
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: blockscout/blockscout
+
+ - name: Add SHORT_SHA env property with commit short sha
+ run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}
+ platforms: |
+ linux/amd64
+ linux/arm64/v8
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }}
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
+ CHAIN_TYPE=suave
\ No newline at end of file
diff --git a/.github/workflows/publish-docker-image-for-xdai.yml b/.github/workflows/publish-docker-image-for-xdai.yml
new file mode 100644
index 000000000000..b53fe18fc50f
--- /dev/null
+++ b/.github/workflows/publish-docker-image-for-xdai.yml
@@ -0,0 +1,61 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: Gnosis chain Publish Docker image
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - production-xdai
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ DOCKER_CHAIN_NAME: xdai
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: blockscout/blockscout
+
+ - name: Add SHORT_SHA env property with commit short sha
+ run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:latest, blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ DISABLE_BRIDGE_MARKET_CAP_UPDATER=false
+ CACHE_BRIDGE_MARKET_CAP_UPDATE_INTERVAL=
+ SENTRY_DSN_CLIENT_GNOSIS=${{ secrets.SENTRY_DSN_CLIENT_GNOSIS }}
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }}
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
\ No newline at end of file
diff --git a/.github/workflows/publish-docker-image-for-zkevm.yml b/.github/workflows/publish-docker-image-for-zkevm.yml
new file mode 100644
index 000000000000..0e7072a3940e
--- /dev/null
+++ b/.github/workflows/publish-docker-image-for-zkevm.yml
@@ -0,0 +1,56 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: Zkevm publish Docker image
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - production-zkevm
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ DOCKER_CHAIN_NAME: zkevm
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v3
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v4
+ with:
+ images: blockscout/blockscout
+
+ - name: Add SHORT_SHA env property with commit short sha
+ run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }}
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
+ CHAIN_TYPE=polygon_zkevm
\ No newline at end of file
diff --git a/.github/workflows/publish-docker-image-for-zksync.yml b/.github/workflows/publish-docker-image-for-zksync.yml
new file mode 100644
index 000000000000..04cdf569a63d
--- /dev/null
+++ b/.github/workflows/publish-docker-image-for-zksync.yml
@@ -0,0 +1,55 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: Zksync publish Docker image
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - production-zksync
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ DOCKER_CHAIN_NAME: zksync
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: blockscout/blockscout
+
+ - name: Add SHORT_SHA env property with commit short sha
+ run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:latest, blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }}
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
\ No newline at end of file
diff --git a/.github/workflows/publish-docker-image-release.yml b/.github/workflows/publish-docker-image-release.yml
deleted file mode 100644
index 031b197627e1..000000000000
--- a/.github/workflows/publish-docker-image-release.yml
+++ /dev/null
@@ -1,117 +0,0 @@
-# This workflow uses actions that are not certified by GitHub.
-# They are provided by a third-party and are governed by
-# separate terms of service, privacy policy, and support
-# documentation.
-
-name: Publish Docker image
-
-on:
- release:
- types: [published]
-
-env:
- OTP_VERSION: '25.2.1'
- ELIXIR_VERSION: '1.14.3'
-
-jobs:
- push_to_registry:
- name: Push Docker image to Docker Hub
- runs-on: ubuntu-latest
- env:
- RELEASE_VERSION: 5.1.5
- steps:
- - name: Check out the repo
- uses: actions/checkout@v3
-
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v2
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v2
-
- - name: Log in to Docker Hub
- uses: docker/login-action@v2
- with:
- username: ${{ secrets.DOCKER_USERNAME }}
- password: ${{ secrets.DOCKER_PASSWORD }}
-
- - name: Extract metadata (tags, labels) for Docker
- id: meta
- uses: docker/metadata-action@v4
- with:
- images: blockscout/blockscout
-
- - name: Build & Push Docker image
- uses: docker/build-push-action@v3
- with:
- context: .
- file: ./docker/Dockerfile
- push: true
- cache-from: type=registry,ref=blockscout/blockscout:buildcache
- cache-to: type=registry,ref=blockscout/blockscout:buildcache,mode=max
- tags: blockscout/blockscout:latest, blockscout/blockscout:${{ env.RELEASE_VERSION }}
- platforms: |
- linux/arm64
- linux/amd64
- build-args: |
- CACHE_EXCHANGE_RATES_PERIOD=
- API_V1_READ_METHODS_DISABLED=false
- DISABLE_WEBAPP=false
- API_V1_WRITE_METHODS_DISABLED=false
- CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
- ADMIN_PANEL_ENABLED=false
- DECODE_NOT_A_CONTRACT_CALLS=false
- MIXPANEL_URL=
- MIXPANEL_TOKEN=
- AMPLITUDE_URL=
- AMPLITUDE_API_KEY=
- CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
- BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta
- RELEASE_VERSION=${{ env.RELEASE_VERSION }}
-
- merge-master-after-release:
- name: Merge 'master' to specific branch after release
- runs-on: ubuntu-latest
- env:
- BRANCHES: |
- production-core-stg
- production-sokol-stg
- production-eth-stg-experimental
- production-eth-goerli-stg
- production-lukso-stg
- production-optimism-goerli-stg
- production-optimism-mainnet-stg
- production-optimism-opcraft-stg
- production-xdai-stg
- production-gc-chiado-stg
- production-polygon-supernets-stg
- production-base-goerli-stg
- production-rsk-stg
- production-neon-stg
- steps:
- - uses: actions/checkout@v2
- - name: Set Git config
- run: |
- git config --local user.email "actions@github.com"
- git config --local user.name "Github Actions"
- - name: Merge master back after release
- run: |
- git fetch --unshallow
- touch errors.txt
- for branch in $BRANCHES;
- do
- git reset --merge
- git checkout master
- git fetch origin
- echo $branch
- git ls-remote --exit-code --heads origin $branch || { echo $branch >> errors.txt; continue; }
- echo "Merge 'master' to $branch"
- git checkout $branch
- git pull || { echo $branch >> errors.txt; continue; }
- git merge --no-ff master -m "Auto-merge master back to $branch" || { echo $branch >> errors.txt; continue; }
- git push || { echo $branch >> errors.txt; continue; }
- git checkout master;
- done
- [ -s errors.txt ] && echo "There are problems with merging 'master' to branches:" || echo "Errors file is empty"
- cat errors.txt
- [ ! -s errors.txt ]
diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/publish-docker-image-staging-on-demand.yml
similarity index 53%
rename from .github/workflows/e2e-tests.yml
rename to .github/workflows/publish-docker-image-staging-on-demand.yml
index b3f0cc07d367..090a6be9d782 100644
--- a/.github/workflows/e2e-tests.yml
+++ b/.github/workflows/publish-docker-image-staging-on-demand.yml
@@ -1,22 +1,21 @@
-name: Run E2E tests k8s
+name: Publish Docker image to staging on demand
on:
- pull_request_review:
- types: [submitted]
workflow_dispatch:
-
+ push:
+ branches:
+ - staging
+ paths-ignore:
+ - 'CHANGELOG.md'
+ - '**/README.md'
+ - 'docker-compose/*'
env:
- K8S_LOCAL_PORT: ${{ secrets.K8S_LOCAL_PORT }}
- K8S_HOST: ${{ secrets.K8S_HOST }}
- BASTION_HOST: ${{ secrets.BASTION_HOST }}
- K8S_PORT: ${{ secrets.K8S_PORT }}
- USERNAME: ${{ secrets.USERNAME }}
- BASTION_SSH_KEY: ${{secrets.BASTION_SSH_KEY}}
- RELEASE_VERSION: 5.1.5
+ OTP_VERSION: '25.2.1'
+ ELIXIR_VERSION: '1.14.5'
+ RELEASE_VERSION: 6.0.0
jobs:
push_to_registry:
- if: github.event.review.state == 'approved'
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
outputs:
@@ -24,43 +23,41 @@ jobs:
short-sha: ${{ steps.output-step.outputs.short-sha }}
steps:
- name: Check out the repo
- uses: actions/checkout@v3
-
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v2
+ uses: actions/checkout@v4
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v2
+ uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
- uses: docker/login-action@v2
+ uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker
id: meta
- uses: docker/metadata-action@v4
+ uses: docker/metadata-action@v5
with:
- images: blockscout/blockscout
+ images: blockscout/blockscout-staging
- name: Add SHORT_SHA env property with commit short sha
run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
- name: Add outputs
run: |
+ echo "::set-output name=release-version::${{ env.NEXT_RELEASE_VERSION }}"
echo "::set-output name=short-sha::${{ env.SHORT_SHA }}"
id: output-step
- name: Build and push Docker image
- uses: docker/build-push-action@v3
+ uses: docker/build-push-action@v5
with:
context: .
file: ./docker/Dockerfile
push: true
- cache-from: type=registry,ref=blockscout/blockscout:pr-buildcache
- cache-to: type=registry,ref=blockscout/blockscout:pr-buildcache,mode=max
- tags: blockscout/blockscout:pr-${{ env.SHORT_SHA }}
+ cache-from: type=registry,ref=blockscout/blockscout:buildcache
+ cache-to: type=registry,ref=blockscout/blockscout:buildcache,mode=max
+ tags: blockscout/blockscout-staging:latest, blockscout/blockscout-staging:${{ env.RELEASE_VERSION }}.commit.${{ env.SHORT_SHA }}
build-args: |
CACHE_EXCHANGE_RATES_PERIOD=
API_V1_READ_METHODS_DISABLED=false
@@ -76,15 +73,3 @@ jobs:
CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }}
RELEASE_VERSION=${{ env.RELEASE_VERSION }}
-
- deploy_and_tests:
- needs: push_to_registry
- if: github.event.review.state == 'approved'
- uses: blockscout/blockscout-ci-cd/.github/workflows/e2e_new.yaml@master
- with:
- blockscoutImage: blockscout/blockscout:pr-${{ needs.push_to_registry.outputs.short-sha }}
- blockscoutIngressHost: e2e-blockscout-$GITHUB_SHA_SHORT
- frontendIngressHost: e2e-blockscout-$GITHUB_SHA_SHORT
- gethIngressHost: e2e-geth-$GITHUB_SHA_SHORT
- scVerifierIngressHost: e2e-sc-verifier-$GITHUB_SHA_SHORT
- secrets: inherit
diff --git a/.github/workflows/release-additional.yml b/.github/workflows/release-additional.yml
new file mode 100644
index 000000000000..f0912540dc69
--- /dev/null
+++ b/.github/workflows/release-additional.yml
@@ -0,0 +1,105 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: Release additional
+
+on:
+ release:
+ types: [published]
+
+env:
+ OTP_VERSION: '25.2.1'
+ ELIXIR_VERSION: '1.14.5'
+
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: blockscout/blockscout
+
+ - name: Build and push Docker image for Rootstock
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-rsk:latest, blockscout/blockscout-rsk:${{ env.RELEASE_VERSION }}
+ platforms: |
+ linux/amd64
+ linux/arm64/v8
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
+ CHAIN_TYPE=rsk
+
+ - name: Build and push Docker image for Polygon Edge
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-polygon-edge:latest, blockscout/blockscout-polygon-edge:${{ env.RELEASE_VERSION }}
+ platforms: |
+ linux/amd64
+ linux/arm64/v8
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
+ CHAIN_TYPE=polygon_edge
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-stability:latest, blockscout/blockscout-stability:${{ env.RELEASE_VERSION }}
+ platforms: |
+ linux/amd64
+ linux/arm64/v8
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
+ CHAIN_TYPE=stability
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 000000000000..ded48ce497f3
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,165 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: Release
+
+on:
+ release:
+ types: [published]
+
+env:
+ OTP_VERSION: '25.2.1'
+ ELIXIR_VERSION: '1.14.5'
+
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: 6.0.0
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: blockscout/blockscout
+
+ - name: Build & Push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ cache-from: type=registry,ref=blockscout/blockscout:buildcache
+ cache-to: type=registry,ref=blockscout/blockscout:buildcache,mode=max
+ tags: blockscout/blockscout:latest, blockscout/blockscout:${{ env.RELEASE_VERSION }}
+ platforms: |
+ linux/amd64
+ linux/arm64/v8
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ DECODE_NOT_A_CONTRACT_CALLS=false
+ MIXPANEL_URL=
+ MIXPANEL_TOKEN=
+ AMPLITUDE_URL=
+ AMPLITUDE_API_KEY=
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
+
+ - name: Build and push Docker image for zkEVM
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-zkevm:latest, blockscout/blockscout-zkevm:${{ env.RELEASE_VERSION }}
+ platforms: |
+ linux/amd64
+ linux/arm64/v8
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
+ CHAIN_TYPE=polygon_zkevm
+
+ - name: Build and push Docker image for SUAVE
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./docker/Dockerfile
+ push: true
+ tags: blockscout/blockscout-suave:latest, blockscout/blockscout-suave:${{ env.RELEASE_VERSION }}
+ platforms: |
+ linux/amd64
+ linux/arm64/v8
+ build-args: |
+ CACHE_EXCHANGE_RATES_PERIOD=
+ API_V1_READ_METHODS_DISABLED=false
+ DISABLE_WEBAPP=false
+ API_V1_WRITE_METHODS_DISABLED=false
+ CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=
+ ADMIN_PANEL_ENABLED=false
+ CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=
+ BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta
+ RELEASE_VERSION=${{ env.RELEASE_VERSION }}
+ CHAIN_TYPE=suave
+
+ - name: Send release announcement to Slack workflow
+ id: slack
+ uses: slackapi/slack-github-action@v1.24.0
+ with:
+ payload: |
+ {
+ "release-version": "${{ env.RELEASE_VERSION }}",
+ "release-link": "https://github.com/blockscout/blockscout/releases/tag/v${{ env.RELEASE_VERSION }}-beta"
+ }
+ env:
+ SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
+
+ # merge-master-after-release:
+ # name: Merge 'master' to specific branch after release
+ # runs-on: ubuntu-latest
+ # env:
+ # BRANCHES: |
+ # production-core
+ # production-sokol
+ # production-eth-experimental
+ # production-eth-goerli
+ # production-lukso
+ # production-xdai
+ # production-polygon-supernets
+ # production-rsk
+ # production-immutable
+ # steps:
+ # - uses: actions/checkout@v4
+ # - name: Set Git config
+ # run: |
+ # git config --local user.email "actions@github.com"
+ # git config --local user.name "Github Actions"
+ # - name: Merge master back after release
+ # run: |
+ # git fetch --unshallow
+ # touch errors.txt
+ # for branch in $BRANCHES;
+ # do
+ # git reset --merge
+ # git checkout master
+ # git fetch origin
+ # echo $branch
+ # git ls-remote --exit-code --heads origin $branch || { echo $branch >> errors.txt; continue; }
+ # echo "Merge 'master' to $branch"
+ # git checkout $branch
+ # git pull || { echo $branch >> errors.txt; continue; }
+ # git merge --no-ff master -m "Auto-merge master back to $branch" || { echo $branch >> errors.txt; continue; }
+ # git push || { echo $branch >> errors.txt; continue; }
+ # git checkout master;
+ # done
+ # [ -s errors.txt ] && echo "There are problems with merging 'master' to branches:" || echo "Errors file is empty"
+ # cat errors.txt
+ # [ ! -s errors.txt ]
diff --git a/.gitignore b/.gitignore
index 77e0d82c4cce..bfd06c0a3aac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,8 +51,8 @@ dump.rdb
/docker-compose/services/blockscout-db-data
/docker-compose/services/stats-db-data
/docker-compose/services/redis-data
+/docker-compose/services/logs
/docker-compose/tmp
-/docker-compose/logs
.idea/
*.iml
@@ -60,3 +60,8 @@ dump.rdb
.vscode
**.dec**
+
+*.env
+*.env.example
+*.env.local
+*.env.staging
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 000000000000..505db0421db5
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,5 @@
+repos:
+ - repo: https://github.com/gitleaks/gitleaks
+ rev: v8.17.0
+ hooks:
+ - id: gitleaks
\ No newline at end of file
diff --git a/.tool-versions b/.tool-versions
index ac759f5a58da..13dabe9920d1 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,3 +1,3 @@
-elixir 1.14.4-otp-25
-erlang 25.3
-nodejs 18.13.0
+elixir 1.14.5-otp-25
+erlang 25.3.2.6
+nodejs 18.17.1
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1e005d61469a..bef7d59c8500 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,17 +4,750 @@
### Features
+- [#9155](https://github.com/blockscout/blockscout/pull/9155) - Allow bypassing avg block time in proxy implementation re-fetch ttl calculation
+- [#9131](https://github.com/blockscout/blockscout/pull/9131) - Merge addresses stage with address referencing
+- [#9072](https://github.com/blockscout/blockscout/pull/9072) - Add tracing by block logic for geth
+- [#9056](https://github.com/blockscout/blockscout/pull/9056) - Noves.fi API proxy
+- [#9158](https://github.com/blockscout/blockscout/pull/9158) - Increase shared memory for PostgreSQL containers
+
+### Fixes
+
+- [#9125](https://github.com/blockscout/blockscout/pull/9125) - Fix Explorer.Chain.Cache.GasPriceOracle.merge_fees
+- [#9110](https://github.com/blockscout/blockscout/pull/9110) - Improve update_in in gas tracker
+- [#9109](https://github.com/blockscout/blockscout/pull/9109) - Return current exchange rate in api/v2/stats
+- [#9102](https://github.com/blockscout/blockscout/pull/9102) - Fix some log topics for Suave and Polygon Edge
+- [#9075](https://github.com/blockscout/blockscout/pull/9075) - Fix fetching contract codes
+- [#9073](https://github.com/blockscout/blockscout/pull/9073) - Allow payable function with output appear in the Read tab
+- [#9069](https://github.com/blockscout/blockscout/pull/9069) - Fetch realtime coin balances only for addresses for which it has changed
+
+### Chore
+
+
+ Dependencies version bumps
+
+
+
+## 6.0.0
+
+### Features
+
+- [#9112](https://github.com/blockscout/blockscout/pull/9112) - Add specific url for eth_call
+- [#9044](https://github.com/blockscout/blockscout/pull/9044) - Expand gas price oracle functionality
+
+### Fixes
+
+- [#9113](https://github.com/blockscout/blockscout/pull/9113) - Fix migrators cache updating
+- [#9101](https://github.com/blockscout/blockscout/pull/9101) - Fix migration_finished? logic
+- [#9062](https://github.com/blockscout/blockscout/pull/9062) - Fix blockscout-ens integration
+- [#9061](https://github.com/blockscout/blockscout/pull/9061) - Arbitrum allow tx receipt gasUsedForL1 field
+- [#8812](https://github.com/blockscout/blockscout/pull/8812) - Update existing tokens type if got transfer with higher type priority
+
+### Chore
+
+- [#9055](https://github.com/blockscout/blockscout/pull/9055) - Add ASC indices for logs, token transfers, transactions
+- [#9038](https://github.com/blockscout/blockscout/pull/9038) - Token type filling migrations
+- [#9009](https://github.com/blockscout/blockscout/pull/9009) - Index for block refetch_needed
+- [#9007](https://github.com/blockscout/blockscout/pull/9007) - Drop logs type index
+- [#9006](https://github.com/blockscout/blockscout/pull/9006) - Drop unused indexes on address_current_token_balances table
+- [#9005](https://github.com/blockscout/blockscout/pull/9005) - Drop unused token_id column from token_transfers table and indexes based on this column
+- [#9000](https://github.com/blockscout/blockscout/pull/9000) - Change log topic type in the DB to bytea
+- [#8996](https://github.com/blockscout/blockscout/pull/8996) - Refine token transfers token ids index
+- [#5322](https://github.com/blockscout/blockscout/pull/5322) - DB denormalization: block consensus and timestamp in transaction table
+
+
+ Dependencies version bumps
+
+- [#9059](https://github.com/blockscout/blockscout/pull/9059) - Bump redux from 5.0.0 to 5.0.1 in /apps/block_scout_web/assets
+- [#9057](https://github.com/blockscout/blockscout/pull/9057) - Bump benchee from 1.2.0 to 1.3.0
+- [#9060](https://github.com/blockscout/blockscout/pull/9060) - Bump @amplitude/analytics-browser from 2.3.7 to 2.3.8 in /apps/block_scout_web/assets
+- [#9084](https://github.com/blockscout/blockscout/pull/9084) - Bump @babel/preset-env from 7.23.6 to 7.23.7 in /apps/block_scout_web/assets
+- [#9083](https://github.com/blockscout/blockscout/pull/9083) - Bump @babel/core from 7.23.6 to 7.23.7 in /apps/block_scout_web/assets
+- [#9086](https://github.com/blockscout/blockscout/pull/9086) - Bump core-js from 3.34.0 to 3.35.0 in /apps/block_scout_web/assets
+- [#9081](https://github.com/blockscout/blockscout/pull/9081) - Bump sweetalert2 from 11.10.1 to 11.10.2 in /apps/block_scout_web/assets
+- [#9085](https://github.com/blockscout/blockscout/pull/9085) - Bump moment from 2.29.4 to 2.30.1 in /apps/block_scout_web/assets
+- [#9087](https://github.com/blockscout/blockscout/pull/9087) - Bump postcss-loader from 7.3.3 to 7.3.4 in /apps/block_scout_web/assets
+- [#9082](https://github.com/blockscout/blockscout/pull/9082) - Bump sass-loader from 13.3.2 to 13.3.3 in /apps/block_scout_web/assets
+- [#9088](https://github.com/blockscout/blockscout/pull/9088) - Bump sass from 1.69.5 to 1.69.6 in /apps/block_scout_web/assets
+
+
+
+## 5.4.0-beta
+
+### Features
+
+- [#9018](https://github.com/blockscout/blockscout/pull/9018) - Add SmartContractRealtimeEventHandler
+- [#8997](https://github.com/blockscout/blockscout/pull/8997) - Isolate throttable error count by request method
+- [#8975](https://github.com/blockscout/blockscout/pull/8975) - Add EIP-4844 compatibility (not full support yet)
+- [#8972](https://github.com/blockscout/blockscout/pull/8972) - BENS integration
+- [#8960](https://github.com/blockscout/blockscout/pull/8960) - TRACE_BLOCK_RANGES env var
+- [#8957](https://github.com/blockscout/blockscout/pull/8957) - Add Tx Interpreter Service integration
+
+### Fixes
+
+- [#9039](https://github.com/blockscout/blockscout/pull/9039) - Fix tx input decoding in tx summary microservice request
+- [#9035](https://github.com/blockscout/blockscout/pull/9035) - Handle Postgrex errors on NFT import
+- [#9015](https://github.com/blockscout/blockscout/pull/9015) - Optimize NFT owner preload
+- [#9013](https://github.com/blockscout/blockscout/pull/9013) - Speed up `Indexer.Fetcher.TokenInstance.LegacySanitize`
+- [#8969](https://github.com/blockscout/blockscout/pull/8969) - Support legacy paging options for address transaction endpoint
+- [#8965](https://github.com/blockscout/blockscout/pull/8965) - Set poll: false for internal transactions fetcher
+- [#8955](https://github.com/blockscout/blockscout/pull/8955) - Remove daily balances updating from BlockReward fetcher
+- [#8846](https://github.com/blockscout/blockscout/pull/8846) - Handle nil gas_price at address view
+
+### Chore
+
+- [#9094](https://github.com/blockscout/blockscout/pull/9094) - Improve exchange rates logging
+- [#9014](https://github.com/blockscout/blockscout/pull/9014) - Decrease amount of NFT in address collection: 15 -> 9
+- [#8994](https://github.com/blockscout/blockscout/pull/8994) - Refactor transactions event preloads
+- [#8991](https://github.com/blockscout/blockscout/pull/8991) - Manage DB queue target via runtime env var
+
+
+ Dependencies version bumps
+
+- [#8986](https://github.com/blockscout/blockscout/pull/8986) - Bump chart.js from 4.4.0 to 4.4.1 in /apps/block_scout_web/assets
+- [#8982](https://github.com/blockscout/blockscout/pull/8982) - Bump ex_doc from 0.30.9 to 0.31.0
+- [#8987](https://github.com/blockscout/blockscout/pull/8987) - Bump @babel/preset-env from 7.23.5 to 7.23.6 in /apps/block_scout_web/assets
+- [#8984](https://github.com/blockscout/blockscout/pull/8984) - Bump ecto_sql from 3.11.0 to 3.11.1
+- [#8988](https://github.com/blockscout/blockscout/pull/8988) - Bump core-js from 3.33.3 to 3.34.0 in /apps/block_scout_web/assets
+- [#8980](https://github.com/blockscout/blockscout/pull/8980) - Bump exvcr from 0.14.4 to 0.15.0
+- [#8985](https://github.com/blockscout/blockscout/pull/8985) - Bump @babel/core from 7.23.5 to 7.23.6 in /apps/block_scout_web/assets
+- [#9020](https://github.com/blockscout/blockscout/pull/9020) - Bump eslint-plugin-import from 2.29.0 to 2.29.1 in /apps/block_scout_web/assets
+- [#9021](https://github.com/blockscout/blockscout/pull/9021) - Bump eslint from 8.55.0 to 8.56.0 in /apps/block_scout_web/assets
+- [#9019](https://github.com/blockscout/blockscout/pull/9019) - Bump @amplitude/analytics-browser from 2.3.6 to 2.3.7 in /apps/block_scout_web/assets
+
+
+
+## 5.3.3-beta
+
+### Features
+
+- [#8966](https://github.com/blockscout/blockscout/pull/8966) - Add `ACCOUNT_WATCHLIST_NOTIFICATIONS_LIMIT_FOR_30_DAYS`
+- [#8908](https://github.com/blockscout/blockscout/pull/8908) - Solidityscan report API endpoint
+- [#8900](https://github.com/blockscout/blockscout/pull/8900) - Add Compound proxy contract pattern
+- [#8611](https://github.com/blockscout/blockscout/pull/8611) - Implement sorting of smart contracts, address transactions
+
+### Fixes
+
+- [#8959](https://github.com/blockscout/blockscout/pull/8959) - Skip failed instances in Token Instance Owner migrator
+- [#8924](https://github.com/blockscout/blockscout/pull/8924) - Delete invalid current token balances in OnDemand fetcher
+- [#8922](https://github.com/blockscout/blockscout/pull/8922) - Allow call type to be in lowercase
+- [#8917](https://github.com/blockscout/blockscout/pull/8917) - Proxy detection hotfix in API v2
+- [#8915](https://github.com/blockscout/blockscout/pull/8915) - smart-contract: delete embeds_many relation on replace
+- [#8906](https://github.com/blockscout/blockscout/pull/8906) - Fix abi encoded string argument
+- [#8898](https://github.com/blockscout/blockscout/pull/8898) - Enhance method decoding by candidates from DB
+- [#8882](https://github.com/blockscout/blockscout/pull/8882) - Change order of proxy contracts patterns detection: existing popular EIPs to the top of the list
+- [#8707](https://github.com/blockscout/blockscout/pull/8707) - Fix native coin exchange rate with `EXCHANGE_RATES_COINGECKO_COIN_ID`
+
+### Chore
+
+- [#8956](https://github.com/blockscout/blockscout/pull/8956) - Refine docker-compose config structure
+- [#8911](https://github.com/blockscout/blockscout/pull/8911) - Set client_connection_check_interval for main Postgres DB in docker-compose setup
+
+
+ Dependencies version bumps
+
+- [#8863](https://github.com/blockscout/blockscout/pull/8863) - Bump core-js from 3.33.2 to 3.33.3 in /apps/block_scout_web/assets
+- [#8864](https://github.com/blockscout/blockscout/pull/8864) - Bump @amplitude/analytics-browser from 2.3.3 to 2.3.5 in /apps/block_scout_web/assets
+- [#8860](https://github.com/blockscout/blockscout/pull/8860) - Bump ecto_sql from 3.10.2 to 3.11.0
+- [#8896](https://github.com/blockscout/blockscout/pull/8896) - Bump httpoison from 2.2.0 to 2.2.1
+- [#8867](https://github.com/blockscout/blockscout/pull/8867) - Bump mixpanel-browser from 2.47.0 to 2.48.1 in /apps/block_scout_web/assets
+- [#8865](https://github.com/blockscout/blockscout/pull/8865) - Bump eslint from 8.53.0 to 8.54.0 in /apps/block_scout_web/assets
+- [#8866](https://github.com/blockscout/blockscout/pull/8866) - Bump sweetalert2 from 11.9.0 to 11.10.1 in /apps/block_scout_web/assets
+- [#8897](https://github.com/blockscout/blockscout/pull/8897) - Bump prometheus from 4.10.0 to 4.11.0
+- [#8859](https://github.com/blockscout/blockscout/pull/8859) - Bump absinthe from 1.7.5 to 1.7.6
+- [#8858](https://github.com/blockscout/blockscout/pull/8858) - Bump ex_json_schema from 0.10.1 to 0.10.2
+- [#8943](https://github.com/blockscout/blockscout/pull/8943) - Bump postgrex from 0.17.3 to 0.17.4
+- [#8939](https://github.com/blockscout/blockscout/pull/8939) - Bump @babel/core from 7.23.3 to 7.23.5 in /apps/block_scout_web/assets
+- [#8936](https://github.com/blockscout/blockscout/pull/8936) - Bump eslint from 8.54.0 to 8.55.0 in /apps/block_scout_web/assets
+- [#8940](https://github.com/blockscout/blockscout/pull/8940) - Bump photoswipe from 5.4.2 to 5.4.3 in /apps/block_scout_web/assets
+- [#8938](https://github.com/blockscout/blockscout/pull/8938) - Bump @babel/preset-env from 7.23.3 to 7.23.5 in /apps/block_scout_web/assets
+- [#8935](https://github.com/blockscout/blockscout/pull/8935) - Bump @amplitude/analytics-browser from 2.3.5 to 2.3.6 in /apps/block_scout_web/assets
+- [#8937](https://github.com/blockscout/blockscout/pull/8937) - Bump redux from 4.2.1 to 5.0.0 in /apps/block_scout_web/assets
+- [#8942](https://github.com/blockscout/blockscout/pull/8942) - Bump gettext from 0.23.1 to 0.24.0
+- [#8934](https://github.com/blockscout/blockscout/pull/8934) - Bump @fortawesome/fontawesome-free from 6.4.2 to 6.5.1 in /apps/block_scout_web/assets
+- [#8933](https://github.com/blockscout/blockscout/pull/8933) - Bump postcss from 8.4.31 to 8.4.32 in /apps/block_scout_web/assets
+
+
+
+## 5.3.2-beta
+
+### Features
+
+- [#8848](https://github.com/blockscout/blockscout/pull/8848) - Add MainPageRealtimeEventHandler
+- [#8821](https://github.com/blockscout/blockscout/pull/8821) - Add new events to addresses channel: `eth_bytecode_db_lookup_started` and `smart_contract_was_not_verified`
+- [#8795](https://github.com/blockscout/blockscout/pull/8795) - Disable catchup indexer by env
+- [#8768](https://github.com/blockscout/blockscout/pull/8768) - Add possibility to search tokens by address hash
+- [#8750](https://github.com/blockscout/blockscout/pull/8750) - Support new eth-bytecode-db request metadata fields
+- [#8634](https://github.com/blockscout/blockscout/pull/8634) - API v2: NFT for address
+- [#8609](https://github.com/blockscout/blockscout/pull/8609) - Change logs format to JSON; Add endpoint url to the block_scout_web logging
+- [#8558](https://github.com/blockscout/blockscout/pull/8558) - Add CoinBalanceDailyUpdater
+
+### Fixes
+
+- [#8891](https://github.com/blockscout/blockscout/pull/8891) - Fix average block time
+- [#8869](https://github.com/blockscout/blockscout/pull/8869) - Limit TokenBalance fetcher timeout
+- [#8855](https://github.com/blockscout/blockscout/pull/8855) - All transactions count at top addresses page
+- [#8836](https://github.com/blockscout/blockscout/pull/8836) - Safe token update
+- [#8814](https://github.com/blockscout/blockscout/pull/8814) - Improve performance for EOA addresses in `/api/v2/addresses/{address_hash}`
+- [#8813](https://github.com/blockscout/blockscout/pull/8813) - Force verify twin contracts on `/api/v2/import/smart-contracts/{address_hash}`
+- [#8784](https://github.com/blockscout/blockscout/pull/8784) - Fix Indexer.Transform.Addresses for non-Suave setup
+- [#8770](https://github.com/blockscout/blockscout/pull/8770) - Fix for eth_getbalance API v1 endpoint when requesting latest tag
+- [#8765](https://github.com/blockscout/blockscout/pull/8765) - Fix for tvl update in market history when row already exists
+- [#8759](https://github.com/blockscout/blockscout/pull/8759) - Gnosis safe proxy via singleton input
+- [#8752](https://github.com/blockscout/blockscout/pull/8752) - Add `TOKEN_INSTANCE_OWNER_MIGRATION_ENABLED` env
+- [#8724](https://github.com/blockscout/blockscout/pull/8724) - Fix flaky account notifier test
+
+### Chore
+
+- [#8832](https://github.com/blockscout/blockscout/pull/8832) - Log more details in regards 413 error
+- [#8807](https://github.com/blockscout/blockscout/pull/8807) - Smart-contract proxy detection refactoring
+- [#8802](https://github.com/blockscout/blockscout/pull/8802) - Enable API v2 by default
+- [#8742](https://github.com/blockscout/blockscout/pull/8742) - Merge rsk branch into the master branch
+- [#8728](https://github.com/blockscout/blockscout/pull/8728) - Remove repos_list (default value for ecto repos) from Explorer.ReleaseTasks
+
+
+ Dependencies version bumps
+
+- [#8727](https://github.com/blockscout/blockscout/pull/8727) - Bump browserify-sign from 4.2.1 to 4.2.2 in /apps/block_scout_web/assets
+- [#8748](https://github.com/blockscout/blockscout/pull/8748) - Bump sweetalert2 from 11.7.32 to 11.9.0 in /apps/block_scout_web/assets
+- [#8747](https://github.com/blockscout/blockscout/pull/8747) - Bump core-js from 3.33.1 to 3.33.2 in /apps/block_scout_web/assets
+- [#8743](https://github.com/blockscout/blockscout/pull/8743) - Bump solc from 0.8.21 to 0.8.22 in /apps/explorer
+- [#8745](https://github.com/blockscout/blockscout/pull/8745) - Bump tesla from 1.7.0 to 1.8.0
+- [#8749](https://github.com/blockscout/blockscout/pull/8749) - Bump sass from 1.69.4 to 1.69.5 in /apps/block_scout_web/assets
+- [#8744](https://github.com/blockscout/blockscout/pull/8744) - Bump phoenix_ecto from 4.4.2 to 4.4.3
+- [#8746](https://github.com/blockscout/blockscout/pull/8746) - Bump floki from 0.35.1 to 0.35.2
+- [#8793](https://github.com/blockscout/blockscout/pull/8793) - Bump eslint from 8.52.0 to 8.53.0 in /apps/block_scout_web/assets
+- [#8792](https://github.com/blockscout/blockscout/pull/8792) - Bump cldr_utils from 2.24.1 to 2.24.2
+- [#8787](https://github.com/blockscout/blockscout/pull/8787) - Bump ex_cldr_numbers from 2.32.2 to 2.32.3
+- [#8790](https://github.com/blockscout/blockscout/pull/8790) - Bump ex_abi from 0.6.3 to 0.6.4
+- [#8788](https://github.com/blockscout/blockscout/pull/8788) - Bump ex_cldr_units from 3.16.3 to 3.16.4
+- [#8827](https://github.com/blockscout/blockscout/pull/8827) - Bump @babel/core from 7.23.2 to 7.23.3 in /apps/block_scout_web/assets
+- [#8823](https://github.com/blockscout/blockscout/pull/8823) - Bump benchee from 1.1.0 to 1.2.0
+- [#8826](https://github.com/blockscout/blockscout/pull/8826) - Bump luxon from 3.4.3 to 3.4.4 in /apps/block_scout_web/assets
+- [#8824](https://github.com/blockscout/blockscout/pull/8824) - Bump httpoison from 2.1.0 to 2.2.0
+- [#8828](https://github.com/blockscout/blockscout/pull/8828) - Bump @babel/preset-env from 7.23.2 to 7.23.3 in /apps/block_scout_web/assets
+- [#8825](https://github.com/blockscout/blockscout/pull/8825) - Bump solc from 0.8.22 to 0.8.23 in /apps/explorer
+
+
+
+## 5.3.1-beta
+
+### Features
+
+- [#8717](https://github.com/blockscout/blockscout/pull/8717) - Save GasPriceOracle old prices as a fallback
+- [#8696](https://github.com/blockscout/blockscout/pull/8696) - Support tokenSymbol and tokenName in `/api/v2/import/token-info`
+- [#8673](https://github.com/blockscout/blockscout/pull/8673) - Add a window for balances fetching from non-archive node
+- [#8651](https://github.com/blockscout/blockscout/pull/8651) - Add `stability_fee` for CHAIN_TYPE=stability
+- [#8556](https://github.com/blockscout/blockscout/pull/8556) - Suave functional
+- [#8528](https://github.com/blockscout/blockscout/pull/8528) - Account: add pagination + envs for limits
+- [#7584](https://github.com/blockscout/blockscout/pull/7584) - Add Polygon zkEVM batches fetcher
+
+### Fixes
+
+- [#8714](https://github.com/blockscout/blockscout/pull/8714) - Fix sourcify check
+- [#8708](https://github.com/blockscout/blockscout/pull/8708) - CoinBalanceHistory tab: show also tx with gasPrice & gasUsed > 0
+- [#8706](https://github.com/blockscout/blockscout/pull/8706) - Add address name updating on contract re-verification
+- [#8705](https://github.com/blockscout/blockscout/pull/8705) - Fix sourcify enabled flag
+- [#8695](https://github.com/blockscout/blockscout/pull/8695), [#8755](https://github.com/blockscout/blockscout/pull/8755) - Don't override internal transaction error if it's present already
+- [#8685](https://github.com/blockscout/blockscout/pull/8685) - Fix db pool size exceeds Postgres max connections
+- [#8678](https://github.com/blockscout/blockscout/pull/8678) - Fix `is_verified` for `/addresses` and `/smart-contracts`
+
+### Chore
+
+- [#8715](https://github.com/blockscout/blockscout/pull/8715) - Rename `wrapped` field to `requestRecord` for Suave
+
+
+ Dependencies version bumps
+
+- [#8683](https://github.com/blockscout/blockscout/pull/8683) - Bump eslint from 8.51.0 to 8.52.0 in /apps/block_scout_web/assets
+- [#8689](https://github.com/blockscout/blockscout/pull/8689) - Bump ex_abi from 0.6.2 to 0.6.3
+- [#8682](https://github.com/blockscout/blockscout/pull/8682) - Bump core-js from 3.33.0 to 3.33.1 in /apps/block_scout_web/assets
+- [#8680](https://github.com/blockscout/blockscout/pull/8680) - Bump web3 from 1.10.2 to 1.10.3 in /apps/block_scout_web/assets
+- [#8681](https://github.com/blockscout/blockscout/pull/8681) - Bump eslint-plugin-import from 2.28.1 to 2.29.0 in /apps/block_scout_web/assets
+- [#8684](https://github.com/blockscout/blockscout/pull/8684) - Bump @amplitude/analytics-browser from 2.3.2 to 2.3.3 in /apps/block_scout_web/assets
+- [#8679](https://github.com/blockscout/blockscout/pull/8679) - Bump sass from 1.69.3 to 1.69.4 in /apps/block_scout_web/assets
+- [#8687](https://github.com/blockscout/blockscout/pull/8687) - Bump floki from 0.35.0 to 0.35.1
+- [#8693](https://github.com/blockscout/blockscout/pull/8693) - Bump redix from 1.2.3 to 1.3.0
+- [#8688](https://github.com/blockscout/blockscout/pull/8688) - Bump ex_doc from 0.30.7 to 0.30.9
+
+
+
+## 5.3.0-beta
+
+### Features
+
+- [#8512](https://github.com/blockscout/blockscout/pull/8512) - Add caching and improve `/tabs-counters` performance
+- [#8472](https://github.com/blockscout/blockscout/pull/8472) - Integrate `/api/v2/bytecodes/sources:search-all` of `eth_bytecode_db`
+- [#8589](https://github.com/blockscout/blockscout/pull/8589) - DefiLlama TVL source
+- [#8544](https://github.com/blockscout/blockscout/pull/8544) - Fix `nil` `"structLogs"`
+- [#8583](https://github.com/blockscout/blockscout/pull/8583) - Add stats widget for rootstock
+- [#8542](https://github.com/blockscout/blockscout/pull/8542) - Add tracing for rootstock
+- [#8561](https://github.com/blockscout/blockscout/pull/8561), [#8564](https://github.com/blockscout/blockscout/pull/8564) - Get historical market cap data from CoinGecko
+- [#8543](https://github.com/blockscout/blockscout/pull/8543) - Fix polygon tracer
+- [#8386](https://github.com/blockscout/blockscout/pull/8386) - Add `owner_address_hash` to the `token_instances`
+- [#8530](https://github.com/blockscout/blockscout/pull/8530) - Add `block_type` to search results
+- [#8180](https://github.com/blockscout/blockscout/pull/8180) - Deposits and Withdrawals for Polygon Edge
+- [#7996](https://github.com/blockscout/blockscout/pull/7996) - Add CoinBalance fetcher init query limit
+- [#8658](https://github.com/blockscout/blockscout/pull/8658) - Remove block consensus on import fail
+- [#8575](https://github.com/blockscout/blockscout/pull/8575) - Filter token transfers on coin balances updates
+
+### Fixes
+
+- [#8661](https://github.com/blockscout/blockscout/pull/8661) - arm64-compatible docker image
+- [#8649](https://github.com/blockscout/blockscout/pull/8649) - Set max 30sec JSON RPC poll frequency for realtime fetcher when WS is disabled
+- [#8614](https://github.com/blockscout/blockscout/pull/8614) - Disable market history cataloger fetcher when exchange rates are disabled
+- [#8613](https://github.com/blockscout/blockscout/pull/8613) - Refactor parsing of FIRST_BLOCK, LAST_BLOCK, TRACE_FIRST_BLOCK, TRACE_LAST_BLOCK env variables
+- [#8572](https://github.com/blockscout/blockscout/pull/8572) - Refactor docker-compose config
+- [#8552](https://github.com/blockscout/blockscout/pull/8552) - Add CHAIN_TYPE build arg to Dockerfile
+- [#8550](https://github.com/blockscout/blockscout/pull/8550) - Sanitize paging params
+- [#8515](https://github.com/blockscout/blockscout/pull/8515) - Fix `:error.types/0 is undefined` warning
+- [#7959](https://github.com/blockscout/blockscout/pull/7959) - Fix empty batch transfers handling
+- [#8513](https://github.com/blockscout/blockscout/pull/8513) - Don't override transaction status
+- [#8620](https://github.com/blockscout/blockscout/pull/8620) - Fix the display of icons
+- [#8594](https://github.com/blockscout/blockscout/pull/8594) - Fix TokenBalance fetcher retry logic
+
+### Chore
+
+- [#8584](https://github.com/blockscout/blockscout/pull/8584) - Store chain together with cookie hash in Redis
+- [#8579](https://github.com/blockscout/blockscout/pull/8579), [#8590](https://github.com/blockscout/blockscout/pull/8590) - IPFS gateway URL runtime env variable
+- [#8573](https://github.com/blockscout/blockscout/pull/8573) - Update Nginx to proxy all frontend paths
+- [#8290](https://github.com/blockscout/blockscout/pull/8290) - Update Chromedriver version
+- [#8536](https://github.com/blockscout/blockscout/pull/8536), [#8537](https://github.com/blockscout/blockscout/pull/8537), [#8540](https://github.com/blockscout/blockscout/pull/8540), [#8557](https://github.com/blockscout/blockscout/pull/8557) - New issue template
+- [#8529](https://github.com/blockscout/blockscout/pull/8529) - Move PolygonEdge-related migration to the corresponding ecto repository
+- [#8504](https://github.com/blockscout/blockscout/pull/8504) - Deploy new UI through Makefile
+- [#8501](https://github.com/blockscout/blockscout/pull/8501) - Conceal secondary ports in docker compose setup
+
+
+ Dependencies version bumps
+
+- [#8508](https://github.com/blockscout/blockscout/pull/8508) - Bump sass from 1.67.0 to 1.68.0 in /apps/block_scout_web/assets
+- [#8509](https://github.com/blockscout/blockscout/pull/8509) - Bump autoprefixer from 10.4.15 to 10.4.16 in /apps/block_scout_web/assets
+- [#8511](https://github.com/blockscout/blockscout/pull/8511) - Bump mox from 1.0.2 to 1.1.0
+- [#8532](https://github.com/blockscout/blockscout/pull/8532) - Bump eslint from 8.49.0 to 8.50.0 in /apps/block_scout_web/assets
+- [#8533](https://github.com/blockscout/blockscout/pull/8533) - Bump sweetalert2 from 11.7.28 to 11.7.29 in /apps/block_scout_web/assets
+- [#8531](https://github.com/blockscout/blockscout/pull/8531) - Bump ex_cldr_units from 3.16.2 to 3.16.3
+- [#8534](https://github.com/blockscout/blockscout/pull/8534) - Bump @babel/core from 7.22.20 to 7.23.0 in /apps/block_scout_web/assets
+- [#8546](https://github.com/blockscout/blockscout/pull/8546) - Bump sweetalert2 from 11.7.29 to 11.7.31 in /apps/block_scout_web/assets
+- [#8553](https://github.com/blockscout/blockscout/pull/8553) - Bump @amplitude/analytics-browser from 2.3.1 to 2.3.2 in /apps/block_scout_web/assets
+- [#8554](https://github.com/blockscout/blockscout/pull/8554) - https://github.com/blockscout/blockscout/pull/8554
+- [#8547](https://github.com/blockscout/blockscout/pull/8547) - Bump briefly from 678a376 to 51dfe7f
+- [#8567](https://github.com/blockscout/blockscout/pull/8567) - Bump photoswipe from 5.4.1 to 5.4.2 in /apps/block_scout_web/assets
+- [#8566](https://github.com/blockscout/blockscout/pull/8566) - Bump postcss from 8.4.30 to 8.4.31 in /apps/block_scout_web/assets
+- [#7575](https://github.com/blockscout/blockscout/pull/7575) - Bump css-loader from 5.2.7 to 6.8.1 in /apps/block_scout_web/assets
+- [#8569](https://github.com/blockscout/blockscout/pull/8569) - Bump web3 from 1.10.0 to 1.10.2 in /apps/block_scout_web/assets
+- [#8570](https://github.com/blockscout/blockscout/pull/8570) - Bump core-js from 3.32.2 to 3.33.0 in /apps/block_scout_web/assets
+- [#8581](https://github.com/blockscout/blockscout/pull/8581) - Bump credo from 1.7.0 to 1.7.1
+- [#8607](https://github.com/blockscout/blockscout/pull/8607) - Bump sass from 1.68.0 to 1.69.0 in /apps/block_scout_web/assets
+- [#8606](https://github.com/blockscout/blockscout/pull/8606) - Bump highlight.js from 11.8.0 to 11.9.0 in /apps/block_scout_web/assets
+- [#8605](https://github.com/blockscout/blockscout/pull/8605) - Bump eslint from 8.50.0 to 8.51.0 in /apps/block_scout_web/assets
+- [#8608](https://github.com/blockscout/blockscout/pull/8608) - Bump sweetalert2 from 11.7.31 to 11.7.32 in /apps/block_scout_web/assets
+- [#8510](https://github.com/blockscout/blockscout/pull/8510) - Bump hackney from 1.18.1 to 1.19.1
+- [#8637](https://github.com/blockscout/blockscout/pull/8637) - Bump @babel/preset-env from 7.22.20 to 7.23.2 in /apps/block_scout_web/assets
+- [#8639](https://github.com/blockscout/blockscout/pull/8639) - Bump sass from 1.69.0 to 1.69.3 in /apps/block_scout_web/assets
+- [#8643](https://github.com/blockscout/blockscout/pull/8643) - Bump floki from 0.34.3 to 0.35.0
+- [#8641](https://github.com/blockscout/blockscout/pull/8641) - Bump ex_cldr from 2.37.2 to 2.37.4
+- [#8646](https://github.com/blockscout/blockscout/pull/8646) - Bump @babel/traverse from 7.23.0 to 7.23.2 in /apps/block_scout_web/assets
+- [#8636](https://github.com/blockscout/blockscout/pull/8636) - Bump @babel/core from 7.23.0 to 7.23.2 in /apps/block_scout_web/assets
+- [#8645](https://github.com/blockscout/blockscout/pull/8645) - Bump ex_doc from 0.30.6 to 0.30.7
+- [#8638](https://github.com/blockscout/blockscout/pull/8638) - Bump webpack from 5.88.2 to 5.89.0 in /apps/block_scout_web/assets
+- [#8640](https://github.com/blockscout/blockscout/pull/8640) - Bump hackney from 1.19.1 to 1.20.1
+
+
+
+## 5.2.3-beta
+
+### Features
+
+- [#8382](https://github.com/blockscout/blockscout/pull/8382) - Add sitemap.xml
+- [#8313](https://github.com/blockscout/blockscout/pull/8313) - Add batches to TokenInstance fetchers
+- [#8285](https://github.com/blockscout/blockscout/pull/8285), [#8399](https://github.com/blockscout/blockscout/pull/8399) - Add CG/CMC coin price sources
+- [#8181](https://github.com/blockscout/blockscout/pull/8181) - Insert current token balances placeholders along with historical
+- [#8210](https://github.com/blockscout/blockscout/pull/8210) - Drop address foreign keys
+- [#8292](https://github.com/blockscout/blockscout/pull/8292) - Add ETHEREUM_JSONRPC_WAIT_PER_TIMEOUT env var
+- [#8269](https://github.com/blockscout/blockscout/pull/8269) - Don't push back to sequence on catchup exception
+- [#8362](https://github.com/blockscout/blockscout/pull/8362), [#8398](https://github.com/blockscout/blockscout/pull/8398) - Drop token balances tokens foreign key
+
+### Fixes
+
+- [#8446](https://github.com/blockscout/blockscout/pull/8446) - Fix market cap calculation in case of CMC
+- [#8431](https://github.com/blockscout/blockscout/pull/8431) - Fix contracts' output decoding
+- [#8354](https://github.com/blockscout/blockscout/pull/8354) - Hotfix for proper addresses' tokens displaying
+- [#8350](https://github.com/blockscout/blockscout/pull/8350) - Add Base Mainnet support for tx actions
+- [#8282](https://github.com/blockscout/blockscout/pull/8282) - NFT fetcher improvements
+- [#8287](https://github.com/blockscout/blockscout/pull/8287) - Add separate hackney pool for TokenInstance fetchers
+- [#8293](https://github.com/blockscout/blockscout/pull/8293) - Add ETHEREUM_JSONRPC_TRACE_URL for Geth in docker-compose.yml
+- [#8240](https://github.com/blockscout/blockscout/pull/8240) - Refactor and fix paging params in API v2
+- [#8242](https://github.com/blockscout/blockscout/pull/8242) - Fixing visualizer service CORS issue when running docker-compose
+- [#8355](https://github.com/blockscout/blockscout/pull/8355) - Fix current token balances redefining
+- [#8338](https://github.com/blockscout/blockscout/pull/8338) - Fix reorgs query
+- [#8413](https://github.com/blockscout/blockscout/pull/8413) - Put error in last call for STOP opcode
+- [#8447](https://github.com/blockscout/blockscout/pull/8447) - Fix reorg transactions
+
+### Chore
+
+- [#8494](https://github.com/blockscout/blockscout/pull/8494) - Add release announcement in Slack
+- [#8493](https://github.com/blockscout/blockscout/pull/8493) - Fix arm docker image build
+- [#8478](https://github.com/blockscout/blockscout/pull/8478) - Set integration with Blockscout's eth bytecode DB endpoint by default and other enhancements
+- [#8442](https://github.com/blockscout/blockscout/pull/8442) - Unify burn address definition
+- [#8321](https://github.com/blockscout/blockscout/pull/8321) - Add curl into resulting Docker image
+- [#8319](https://github.com/blockscout/blockscout/pull/8319) - Add MIX_ENV: 'prod' to docker-compose
+- [#8281](https://github.com/blockscout/blockscout/pull/8281) - Planned removal of duplicate API endpoints: for CSV export and GraphQL
+
+
+ Dependencies version bumps
+
+- [#8244](https://github.com/blockscout/blockscout/pull/8244) - Bump core-js from 3.32.0 to 3.32.1 in /apps/block_scout_web/assets
+- [#8243](https://github.com/blockscout/blockscout/pull/8243) - Bump sass from 1.65.1 to 1.66.0 in /apps/block_scout_web/assets
+- [#8259](https://github.com/blockscout/blockscout/pull/8259) - Bump sweetalert2 from 11.7.23 to 11.7.27 in /apps/block_scout_web/assets
+- [#8258](https://github.com/blockscout/blockscout/pull/8258) - Bump sass from 1.66.0 to 1.66.1 in /apps/block_scout_web/assets
+- [#8260](https://github.com/blockscout/blockscout/pull/8260) - Bump jest from 29.6.2 to 29.6.3 in /apps/block_scout_web/assets
+- [#8261](https://github.com/blockscout/blockscout/pull/8261) - Bump eslint-plugin-import from 2.28.0 to 2.28.1 in /apps/block_scout_web/assets
+- [#8262](https://github.com/blockscout/blockscout/pull/8262) - Bump jest-environment-jsdom from 29.6.2 to 29.6.3 in /apps/block_scout_web/assets
+- [#8275](https://github.com/blockscout/blockscout/pull/8275) - Bump ecto_sql from 3.10.1 to 3.10.2
+- [#8284](https://github.com/blockscout/blockscout/pull/8284) - Bump luxon from 3.4.0 to 3.4.1 in /apps/block_scout_web/assets
+- [#8294](https://github.com/blockscout/blockscout/pull/8294) - Bump chart.js from 4.3.3 to 4.4.0 in /apps/block_scout_web/assets
+- [#8295](https://github.com/blockscout/blockscout/pull/8295) - Bump jest from 29.6.3 to 29.6.4 in /apps/block_scout_web/assets
+- [#8296](https://github.com/blockscout/blockscout/pull/8296) - Bump jest-environment-jsdom from 29.6.3 to 29.6.4 in /apps/block_scout_web/assets
+- [#8297](https://github.com/blockscout/blockscout/pull/8297) - Bump @babel/core from 7.22.10 to 7.22.11 in /apps/block_scout_web/assets
+- [#8305](https://github.com/blockscout/blockscout/pull/8305) - Bump @amplitude/analytics-browser from 2.2.0 to 2.2.1 in /apps/block_scout_web/assets
+- [#8342](https://github.com/blockscout/blockscout/pull/8342) - Bump postgrex from 0.17.2 to 0.17.3
+- [#8341](https://github.com/blockscout/blockscout/pull/8341) - Bump hackney from 1.18.1 to 1.18.2
+- [#8343](https://github.com/blockscout/blockscout/pull/8343) - Bump @amplitude/analytics-browser from 2.2.1 to 2.2.2 in /apps/block_scout_web/assets
+- [#8344](https://github.com/blockscout/blockscout/pull/8344) - Bump postcss from 8.4.28 to 8.4.29 in /apps/block_scout_web/assets
+- [#8330](https://github.com/blockscout/blockscout/pull/8330) - Bump bignumber.js from 9.1.1 to 9.1.2 in /apps/block_scout_web/assets
+- [#8332](https://github.com/blockscout/blockscout/pull/8332) - Bump jquery from 3.7.0 to 3.7.1 in /apps/block_scout_web/assets
+- [#8329](https://github.com/blockscout/blockscout/pull/8329) - Bump viewerjs from 1.11.4 to 1.11.5 in /apps/block_scout_web/assets
+- [#8328](https://github.com/blockscout/blockscout/pull/8328) - Bump eslint from 8.47.0 to 8.48.0 in /apps/block_scout_web/assets
+- [#8325](https://github.com/blockscout/blockscout/pull/8325) - Bump exvcr from 0.14.3 to 0.14.4
+- [#8323](https://github.com/blockscout/blockscout/pull/8323) - Bump ex_doc from 0.30.5 to 0.30.6
+- [#8322](https://github.com/blockscout/blockscout/pull/8322) - Bump dialyxir from 1.3.0 to 1.4.0
+- [#8326](https://github.com/blockscout/blockscout/pull/8326) - Bump comeonin from 5.3.3 to 5.4.0
+- [#8331](https://github.com/blockscout/blockscout/pull/8331) - Bump luxon from 3.4.1 to 3.4.2 in /apps/block_scout_web/assets
+- [#8324](https://github.com/blockscout/blockscout/pull/8324) - Bump spandex_datadog from 1.3.0 to 1.4.0
+- [#8327](https://github.com/blockscout/blockscout/pull/8327) - Bump bcrypt_elixir from 3.0.1 to 3.1.0
+- [#8358](https://github.com/blockscout/blockscout/pull/8358) - Bump @babel/preset-env from 7.22.10 to 7.22.14 in /apps/block_scout_web/assets
+- [#8365](https://github.com/blockscout/blockscout/pull/8365) - Bump dialyxir from 1.4.0 to 1.4.1
+- [#8374](https://github.com/blockscout/blockscout/pull/8374) - Bump @amplitude/analytics-browser from 2.2.2 to 2.2.3 in /apps/block_scout_web/assets
+- [#8373](https://github.com/blockscout/blockscout/pull/8373) - Bump ex_secp256k1 from 0.7.0 to 0.7.1
+- [#8391](https://github.com/blockscout/blockscout/pull/8391) - Bump @babel/preset-env from 7.22.14 to 7.22.15 in /apps/block_scout_web/assets
+- [#8390](https://github.com/blockscout/blockscout/pull/8390) - Bump photoswipe from 5.3.8 to 5.3.9 in /apps/block_scout_web/assets
+- [#8389](https://github.com/blockscout/blockscout/pull/8389) - Bump @babel/core from 7.22.11 to 7.22.15 in /apps/block_scout_web/assets
+- [#8392](https://github.com/blockscout/blockscout/pull/8392) - Bump ex_cldr_numbers from 2.31.3 to 2.32.0
+- [#8400](https://github.com/blockscout/blockscout/pull/8400) - Bump ex_secp256k1 from 0.7.1 to 0.7.2
+- [#8405](https://github.com/blockscout/blockscout/pull/8405) - Bump luxon from 3.4.2 to 3.4.3 in /apps/block_scout_web/assets
+- [#8404](https://github.com/blockscout/blockscout/pull/8404) - Bump ex_abi from 0.6.0 to 0.6.1
+- [#8410](https://github.com/blockscout/blockscout/pull/8410) - Bump core-js from 3.32.1 to 3.32.2 in /apps/block_scout_web/assets
+- [#8418](https://github.com/blockscout/blockscout/pull/8418) - Bump url from 0.11.1 to 0.11.2 in /apps/block_scout_web/assets
+- [#8416](https://github.com/blockscout/blockscout/pull/8416) - Bump @babel/core from 7.22.15 to 7.22.17 in /apps/block_scout_web/assets
+- [#8419](https://github.com/blockscout/blockscout/pull/8419) - Bump assert from 2.0.0 to 2.1.0 in /apps/block_scout_web/assets
+- [#8417](https://github.com/blockscout/blockscout/pull/8417) - Bump photoswipe from 5.3.9 to 5.4.0 in /apps/block_scout_web/assets
+- [#8441](https://github.com/blockscout/blockscout/pull/8441) - Bump eslint from 8.48.0 to 8.49.0 in /apps/block_scout_web/assets
+- [#8439](https://github.com/blockscout/blockscout/pull/8439) - Bump ex_cldr_numbers from 2.32.0 to 2.32.1
+- [#8444](https://github.com/blockscout/blockscout/pull/8444) - Bump ex_cldr_numbers from 2.32.1 to 2.32.2
+- [#8445](https://github.com/blockscout/blockscout/pull/8445) - Bump ex_abi from 0.6.1 to 0.6.2
+- [#8450](https://github.com/blockscout/blockscout/pull/8450) - Bump jest-environment-jsdom from 29.6.4 to 29.7.0 in /apps/block_scout_web/assets
+- [#8451](https://github.com/blockscout/blockscout/pull/8451) - Bump jest from 29.6.4 to 29.7.0 in /apps/block_scout_web/assets
+- [#8463](https://github.com/blockscout/blockscout/pull/8463) - Bump sass from 1.66.1 to 1.67.0 in /apps/block_scout_web/assets
+- [#8464](https://github.com/blockscout/blockscout/pull/8464) - Bump @babel/core from 7.22.17 to 7.22.19 in /apps/block_scout_web/assets
+- [#8462](https://github.com/blockscout/blockscout/pull/8462) - Bump sweetalert2 from 11.7.27 to 11.7.28 in /apps/block_scout_web/assets
+- [#8479](https://github.com/blockscout/blockscout/pull/8479) - Bump photoswipe from 5.4.0 to 5.4.1 in /apps/block_scout_web/assets
+- [#8483](https://github.com/blockscout/blockscout/pull/8483) - Bump @amplitude/analytics-browser from 2.2.3 to 2.3.1 in /apps/block_scout_web/assets
+- [#8481](https://github.com/blockscout/blockscout/pull/8481) - Bump @babel/preset-env from 7.22.15 to 7.22.20 in /apps/block_scout_web/assets
+- [#8480](https://github.com/blockscout/blockscout/pull/8480) - Bump @babel/core from 7.22.19 to 7.22.20 in /apps/block_scout_web/assets
+- [#8482](https://github.com/blockscout/blockscout/pull/8482) - Bump viewerjs from 1.11.5 to 1.11.6 in /apps/block_scout_web/assets
+- [#8489](https://github.com/blockscout/blockscout/pull/8489) - Bump postcss from 8.4.29 to 8.4.30 in /apps/block_scout_web/assets
+
+
+
+## 5.2.2-beta
+
+### Features
+
+- [#8218](https://github.com/blockscout/blockscout/pull/8218) - Add `/api/v2/search/quick` method
+- [#8202](https://github.com/blockscout/blockscout/pull/8202) - Add `/api/v2/addresses/:address_hash/tabs-counters` endpoint
+- [#8156](https://github.com/blockscout/blockscout/pull/8156) - Add `is_verified_via_admin_panel` property to tokens table
+- [#8165](https://github.com/blockscout/blockscout/pull/8165), [#8201](https://github.com/blockscout/blockscout/pull/8201) - Add broadcast of updated address_current_token_balances
+- [#7952](https://github.com/blockscout/blockscout/pull/7952) - Add parsing constructor arguments for sourcify contracts
+- [#6190](https://github.com/blockscout/blockscout/pull/6190) - Add EIP-1559 support to gas price oracle
+- [#7977](https://github.com/blockscout/blockscout/pull/7977) - GraphQL: extend schema with new field for existing objects
+- [#8158](https://github.com/blockscout/blockscout/pull/8158), [#8164](https://github.com/blockscout/blockscout/pull/8164) - Include unfetched balances in TokenBalanceOnDemand fetcher
+
+### Fixes
+
+- [#8233](https://github.com/blockscout/blockscout/pull/8233) - Fix API v2 broken tx response
+- [#8147](https://github.com/blockscout/blockscout/pull/8147) - Switch sourcify tests from POA Sokol to Gnosis Chiado
+- [#8145](https://github.com/blockscout/blockscout/pull/8145) - Handle negative holders count in API v2
+- [#8040](https://github.com/blockscout/blockscout/pull/8040) - Resolve issue with Docker image for Mac M1/M2
+- [#8060](https://github.com/blockscout/blockscout/pull/8060) - Fix eth_getLogs API endpoint
+- [#8082](https://github.com/blockscout/blockscout/pull/8082), [#8088](https://github.com/blockscout/blockscout/pull/8088) - Fix Rootstock charts API
+- [#7992](https://github.com/blockscout/blockscout/pull/7992) - Fix missing range insert
+- [#8022](https://github.com/blockscout/blockscout/pull/8022) - Don't add reorg block number to missing blocks
+
+### Chore
+
+- [#8222](https://github.com/blockscout/blockscout/pull/8222) - docker-compose for new UI with external backend
+- [#8177](https://github.com/blockscout/blockscout/pull/8177) - Refactor address counter functions
+- [#8183](https://github.com/blockscout/blockscout/pull/8183) - Update frontend envs in order to pass their validation
+- [#8167](https://github.com/blockscout/blockscout/pull/8167) - Manage concurrency for Token and TokenBalance fetcher
+- [#8179](https://github.com/blockscout/blockscout/pull/8179) - Enhance nginx config
+- [#8146](https://github.com/blockscout/blockscout/pull/8146) - Add method_id to write methods in API v2 response
+- [#8105](https://github.com/blockscout/blockscout/pull/8105) - Extend API v1 with endpoints used by new UI
+- [#8104](https://github.com/blockscout/blockscout/pull/8104) - remove "TODO" from API v2 response
+- [#8100](https://github.com/blockscout/blockscout/pull/8100), [#8103](https://github.com/blockscout/blockscout/pull/8103) - Extend docker-compose configs with new config when front is running externally
+- [#8012](https://github.com/blockscout/blockscout/pull/8012) - API v2 smart-contract verification extended logging
+
+
+ Dependencies version bumps
+
+- [#7980](https://github.com/blockscout/blockscout/pull/7980) - Bump solc from 0.8.20 to 0.8.21 in /apps/explorer
+- [#7986](https://github.com/blockscout/blockscout/pull/7986) - Bump sass from 1.63.6 to 1.64.0 in /apps/block_scout_web/assets
+- [#8030](https://github.com/blockscout/blockscout/pull/8030) - Bump sweetalert2 from 11.7.18 to 11.7.20 in /apps/block_scout_web/assets
+- [#8029](https://github.com/blockscout/blockscout/pull/8029) - Bump viewerjs from 1.11.3 to 1.11.4 in /apps/block_scout_web/assets
+- [#8028](https://github.com/blockscout/blockscout/pull/8028) - Bump sass from 1.64.0 to 1.64.1 in /apps/block_scout_web/assets
+- [#8026](https://github.com/blockscout/blockscout/pull/8026) - Bump dataloader from 1.0.10 to 1.0.11
+- [#8036](https://github.com/blockscout/blockscout/pull/8036) - Bump ex_cldr_numbers from 2.31.1 to 2.31.3
+- [#8027](https://github.com/blockscout/blockscout/pull/8027) - Bump absinthe from 1.7.4 to 1.7.5
+- [#8035](https://github.com/blockscout/blockscout/pull/8035) - Bump wallaby from 0.30.4 to 0.30.5
+- [#8038](https://github.com/blockscout/blockscout/pull/8038) - Bump chart.js from 4.3.0 to 4.3.1 in /apps/block_scout_web/assets
+- [#8047](https://github.com/blockscout/blockscout/pull/8047) - Bump chart.js from 4.3.1 to 4.3.2 in /apps/block_scout_web/assets
+- [#8000](https://github.com/blockscout/blockscout/pull/8000) - Bump postcss from 8.4.26 to 8.4.27 in /apps/block_scout_web/assets
+- [#8052](https://github.com/blockscout/blockscout/pull/8052) - Bump @amplitude/analytics-browser from 2.1.2 to 2.1.3 in /apps/block_scout_web/assets
+- [#8054](https://github.com/blockscout/blockscout/pull/8054) - Bump jest-environment-jsdom from 29.6.1 to 29.6.2 in /apps/block_scout_web/assets
+- [#8063](https://github.com/blockscout/blockscout/pull/8063) - Bump eslint from 8.45.0 to 8.46.0 in /apps/block_scout_web/assets
+- [#8066](https://github.com/blockscout/blockscout/pull/8066) - Bump ex_json_schema from 0.9.3 to 0.10.1
+- [#8064](https://github.com/blockscout/blockscout/pull/8064) - Bump core-js from 3.31.1 to 3.32.0 in /apps/block_scout_web/assets
+- [#8053](https://github.com/blockscout/blockscout/pull/8053) - Bump jest from 29.6.1 to 29.6.2 in /apps/block_scout_web/assets
+- [#8065](https://github.com/blockscout/blockscout/pull/8065) - Bump eslint-plugin-import from 2.27.5 to 2.28.0 in /apps/block_scout_web/assets
+- [#8092](https://github.com/blockscout/blockscout/pull/8092) - Bump exvcr from 0.14.1 to 0.14.2
+- [#8091](https://github.com/blockscout/blockscout/pull/8091) - Bump sass from 1.64.1 to 1.64.2 in /apps/block_scout_web/assets
+- [#8114](https://github.com/blockscout/blockscout/pull/8114) - Bump ex_doc from 0.30.3 to 0.30.4
+- [#8115](https://github.com/blockscout/blockscout/pull/8115) - Bump chart.js from 4.3.2 to 4.3.3 in /apps/block_scout_web/assets
+- [#8116](https://github.com/blockscout/blockscout/pull/8116) - Bump @fortawesome/fontawesome-free from 6.4.0 to 6.4.2 in /apps/block_scout_web/assets
+- [#8142](https://github.com/blockscout/blockscout/pull/8142) - Bump sobelow from 0.12.2 to 0.13.0
+- [#8141](https://github.com/blockscout/blockscout/pull/8141) - Bump @babel/core from 7.22.9 to 7.22.10 in /apps/block_scout_web/assets
+- [#8140](https://github.com/blockscout/blockscout/pull/8140) - Bump @babel/preset-env from 7.22.9 to 7.22.10 in /apps/block_scout_web/assets
+- [#8160](https://github.com/blockscout/blockscout/pull/8160) - Bump exvcr from 0.14.2 to 0.14.3
+- [#8159](https://github.com/blockscout/blockscout/pull/8159) - Bump luxon from 3.3.0 to 3.4.0 in /apps/block_scout_web/assets
+- [#8169](https://github.com/blockscout/blockscout/pull/8169) - Bump sass from 1.64.2 to 1.65.1 in /apps/block_scout_web/assets
+- [#8170](https://github.com/blockscout/blockscout/pull/8170) - Bump sweetalert2 from 11.7.20 to 11.7.22 in /apps/block_scout_web/assets
+- [#8188](https://github.com/blockscout/blockscout/pull/8188) - Bump eslint from 8.46.0 to 8.47.0 in /apps/block_scout_web/assets
+- [#8204](https://github.com/blockscout/blockscout/pull/8204) - Bump ex_doc from 0.30.4 to 0.30.5
+- [#8207](https://github.com/blockscout/blockscout/pull/8207) - Bump wallaby from 0.30.5 to 0.30.6
+- [#8212](https://github.com/blockscout/blockscout/pull/8212) - Bump sweetalert2 from 11.7.22 to 11.7.23 in /apps/block_scout_web/assets
+- [#8203](https://github.com/blockscout/blockscout/pull/8203) - Bump autoprefixer from 10.4.14 to 10.4.15 in /apps/block_scout_web/assets
+- [#8214](https://github.com/blockscout/blockscout/pull/8214) - Bump @amplitude/analytics-browser from 2.1.3 to 2.2.0 in /apps/block_scout_web/assets
+- [#8225](https://github.com/blockscout/blockscout/pull/8225) - Bump postcss from 8.4.27 to 8.4.28 in /apps/block_scout_web/assets
+- [#8224](https://github.com/blockscout/blockscout/pull/8224) - Bump gettext from 0.22.3 to 0.23.1
+
+
+
+## 5.2.1-beta
+
+### Features
+
+- [#7970](https://github.com/blockscout/blockscout/pull/7970) - Search improvements: add sorting
+- [#7771](https://github.com/blockscout/blockscout/pull/7771) - CSV export: speed up
+- [#7962](https://github.com/blockscout/blockscout/pull/7962) - Allow indicate CMC id of the coin through env var
+- [#7946](https://github.com/blockscout/blockscout/pull/7946) - API v2 rate limit: Put token to cookies & change /api/v2/key method
+- [#7888](https://github.com/blockscout/blockscout/pull/7888) - Add token balances info to watchlist address response
+- [#7898](https://github.com/blockscout/blockscout/pull/7898) - Add possibility to add extra headers with JSON RPC URL
+- [#7836](https://github.com/blockscout/blockscout/pull/7836) - Improve unverified email flow
+- [#7784](https://github.com/blockscout/blockscout/pull/7784) - Search improvements: Add new fields, light refactoring
+- [#7811](https://github.com/blockscout/blockscout/pull/7811) - Filter addresses before insertion
+- [#7895](https://github.com/blockscout/blockscout/pull/7895) - API v2: Add sorting to tokens page
+- [#7859](https://github.com/blockscout/blockscout/pull/7859) - Add TokenTotalSupplyUpdater
+- [#7873](https://github.com/blockscout/blockscout/pull/7873) - Chunk realtime balances requests
+- [#7927](https://github.com/blockscout/blockscout/pull/7927) - Delete token balances only for blocks that lost consensus
+- [#7947](https://github.com/blockscout/blockscout/pull/7947) - Improve locks acquiring
+
+### Fixes
+
+- [#8187](https://github.com/blockscout/blockscout/pull/8187) - API v1 500 error convert to 404, if requested path is incorrect
+- [#7852](https://github.com/blockscout/blockscout/pull/7852) - Token balances refactoring & fixes
+- [#7872](https://github.com/blockscout/blockscout/pull/7872) - Fix pending gas price in pending tx
+- [#7875](https://github.com/blockscout/blockscout/pull/7875) - Fix twin compiler version
+- [#7825](https://github.com/blockscout/blockscout/pull/7825) - Fix nginx config for the new frontend websockets
+- [#7772](https://github.com/blockscout/blockscout/pull/7772) - Fix parsing of database password period(s)
+- [#7803](https://github.com/blockscout/blockscout/pull/7803) - Fix additional sources and interfaces, save names for vyper contracts
+- [#7758](https://github.com/blockscout/blockscout/pull/7758) - Remove limit for configurable fetchers
+- [#7764](https://github.com/blockscout/blockscout/pull/7764) - Fix missing ranges insertion and deletion logic
+- [#7843](https://github.com/blockscout/blockscout/pull/7843) - Fix created_contract_code_indexed_at updating
+- [#7855](https://github.com/blockscout/blockscout/pull/7855) - Handle internal transactions unique_violation
+- [#7899](https://github.com/blockscout/blockscout/pull/7899) - Fix catchup numbers_to_ranges function
+- [#7951](https://github.com/blockscout/blockscout/pull/7951) - Fix TX url in email notifications on mainnet
+
+### Chore
+
+- [#7963](https://github.com/blockscout/blockscout/pull/7963) - Op Stack: ignore depositNonce
+- [#7954](https://github.com/blockscout/blockscout/pull/7954) - Enhance Account Explorer.Account.Notifier.Email module tests
+- [#7950](https://github.com/blockscout/blockscout/pull/7950) - Add GA CI for Eth Goerli chain
+- [#7934](https://github.com/blockscout/blockscout/pull/7934), [#7936](https://github.com/blockscout/blockscout/pull/7936) - Explicitly set consensus == true in queries (convenient for search), remove logger requirements, where it is not used anymore
+- [#7901](https://github.com/blockscout/blockscout/pull/7901) - Fix Docker image build
+- [#7890](https://github.com/blockscout/blockscout/pull/7890), [#7918](https://github.com/blockscout/blockscout/pull/7918) - Resolve warning: Application.get_env/2 is discouraged in the module body, use Application.compile_env/3 instead
+- [#7863](https://github.com/blockscout/blockscout/pull/7863) - Add max_age for account sessions
+- [#7841](https://github.com/blockscout/blockscout/pull/7841) - CORS setup for docker-compose config with new frontend
+- [#7832](https://github.com/blockscout/blockscout/pull/7832), [#7891](https://github.com/blockscout/blockscout/pull/7891) - API v2: Add block_number, block_hash to logs
+- [#7789](https://github.com/blockscout/blockscout/pull/7789) - Fix test warnings; Fix name of `MICROSERVICE_ETH_BYTECODE_DB_INTERVAL_BETWEEN_LOOKUPS` env variable
+- [#7819](https://github.com/blockscout/blockscout/pull/7819) - Add logging for unknown error verification result
+- [#7781](https://github.com/blockscout/blockscout/pull/7781) - Add `/api/v1/health/liveness` and `/api/v1/health/readiness`
+
+
+ Dependencies version bumps
+
+- [#7759](https://github.com/blockscout/blockscout/pull/7759) - Bump sass from 1.63.4 to 1.63.5 in /apps/block_scout_web/assets
+- [#7760](https://github.com/blockscout/blockscout/pull/7760) - Bump @amplitude/analytics-browser from 2.0.0 to 2.0.1 in /apps/block_scout_web/assets
+- [#7762](https://github.com/blockscout/blockscout/pull/7762) - Bump webpack from 5.87.0 to 5.88.0 in /apps/block_scout_web/assets
+- [#7769](https://github.com/blockscout/blockscout/pull/7769) - Bump sass from 1.63.5 to 1.63.6 in /apps/block_scout_web/assets
+- [#7805](https://github.com/blockscout/blockscout/pull/7805) - Bump ssl_verify_fun from 1.1.6 to 1.1.7
+- [#7812](https://github.com/blockscout/blockscout/pull/7812) - Bump webpack from 5.88.0 to 5.88.1 in /apps/block_scout_web/assets
+- [#7770](https://github.com/blockscout/blockscout/pull/7770) - Bump @amplitude/analytics-browser from 2.0.1 to 2.1.0 in /apps/block_scout_web/assets
+- [#7821](https://github.com/blockscout/blockscout/pull/7821) - Bump absinthe from 1.7.1 to 1.7.3
+- [#7823](https://github.com/blockscout/blockscout/pull/7823) - Bump @amplitude/analytics-browser from 2.1.0 to 2.1.1 in /apps/block_scout_web/assets
+- [#7838](https://github.com/blockscout/blockscout/pull/7838) - Bump gettext from 0.22.2 to 0.22.3
+- [#7840](https://github.com/blockscout/blockscout/pull/7840) - Bump eslint from 8.43.0 to 8.44.0 in /apps/block_scout_web/assets
+- [#7839](https://github.com/blockscout/blockscout/pull/7839) - Bump photoswipe from 5.3.7 to 5.3.8 in /apps/block_scout_web/assets
+- [#7850](https://github.com/blockscout/blockscout/pull/7850) - Bump jest-environment-jsdom from 29.5.0 to 29.6.0 in /apps/block_scout_web/assets
+- [#7848](https://github.com/blockscout/blockscout/pull/7848) - Bump @amplitude/analytics-browser from 2.1.1 to 2.1.2 in /apps/block_scout_web/assets
+- [#7847](https://github.com/blockscout/blockscout/pull/7847) - Bump @babel/core from 7.22.5 to 7.22.6 in /apps/block_scout_web/assets
+- [#7846](https://github.com/blockscout/blockscout/pull/7846) - Bump @babel/preset-env from 7.22.5 to 7.22.6 in /apps/block_scout_web/assets
+- [#7856](https://github.com/blockscout/blockscout/pull/7856) - Bump ex_cldr from 2.37.1 to 2.37.2
+- [#7870](https://github.com/blockscout/blockscout/pull/7870) - Bump jest from 29.5.0 to 29.6.1 in /apps/block_scout_web/assets
+- [#7867](https://github.com/blockscout/blockscout/pull/7867) - Bump postcss from 8.4.24 to 8.4.25 in /apps/block_scout_web/assets
+- [#7871](https://github.com/blockscout/blockscout/pull/7871) - Bump @babel/core from 7.22.6 to 7.22.8 in /apps/block_scout_web/assets
+- [#7868](https://github.com/blockscout/blockscout/pull/7868) - Bump jest-environment-jsdom from 29.6.0 to 29.6.1 in /apps/block_scout_web/assets
+- [#7866](https://github.com/blockscout/blockscout/pull/7866) - Bump @babel/preset-env from 7.22.6 to 7.22.7 in /apps/block_scout_web/assets
+- [#7869](https://github.com/blockscout/blockscout/pull/7869) - Bump core-js from 3.31.0 to 3.31.1 in /apps/block_scout_web/assets
+- [#7884](https://github.com/blockscout/blockscout/pull/7884) - Bump ecto from 3.10.2 to 3.10.3
+- [#7882](https://github.com/blockscout/blockscout/pull/7882) - Bump jason from 1.4.0 to 1.4.1
+- [#7880](https://github.com/blockscout/blockscout/pull/7880) - Bump absinthe from 1.7.3 to 1.7.4
+- [#7879](https://github.com/blockscout/blockscout/pull/7879) - Bump babel-loader from 9.1.2 to 9.1.3 in /apps/block_scout_web/assets
+- [#7881](https://github.com/blockscout/blockscout/pull/7881) - Bump ex_cldr_numbers from 2.31.1 to 2.31.2
+- [#7883](https://github.com/blockscout/blockscout/pull/7883) - Bump ex_doc from 0.29.4 to 0.30.1
+- [#7916](https://github.com/blockscout/blockscout/pull/7916) - Bump semver from 5.7.1 to 5.7.2 in /apps/explorer
+- [#7912](https://github.com/blockscout/blockscout/pull/7912) - Bump sweetalert2 from 11.7.12 to 11.7.16 in /apps/block_scout_web/assets
+- [#7913](https://github.com/blockscout/blockscout/pull/7913) - Bump ex_doc from 0.30.1 to 0.30.2
+- [#7923](https://github.com/blockscout/blockscout/pull/7923) - Bump postgrex from 0.17.1 to 0.17.2
+- [#7921](https://github.com/blockscout/blockscout/pull/7921) - Bump @babel/preset-env from 7.22.7 to 7.22.9 in /apps/block_scout_web/assets
+- [#7922](https://github.com/blockscout/blockscout/pull/7922) - Bump @babel/core from 7.22.8 to 7.22.9 in /apps/block_scout_web/assets
+- [#7931](https://github.com/blockscout/blockscout/pull/7931) - Bump wallaby from 0.30.3 to 0.30.4
+- [#7940](https://github.com/blockscout/blockscout/pull/7940) - Bump postcss from 8.4.25 to 8.4.26 in /apps/block_scout_web/assets
+- [#7939](https://github.com/blockscout/blockscout/pull/7939) - Bump eslint from 8.44.0 to 8.45.0 in /apps/block_scout_web/assets
+- [#7955](https://github.com/blockscout/blockscout/pull/7955) - Bump sweetalert2 from 11.7.16 to 11.7.18 in /apps/block_scout_web/assets
+- [#7958](https://github.com/blockscout/blockscout/pull/7958) - Bump ex_doc from 0.30.2 to 0.30.3
+- [#7965](https://github.com/blockscout/blockscout/pull/7965) - Bump webpack from 5.88.1 to 5.88.2 in /apps/block_scout_web/assets
+- [#7972](https://github.com/blockscout/blockscout/pull/7972) - Bump word-wrap from 1.2.3 to 1.2.4 in /apps/block_scout_web/assets
+
+
+## 5.2.0-beta
+
+### Features
+
+- [#7502](https://github.com/blockscout/blockscout/pull/7502) - Improve performance of some methods, endpoints and SQL queries
+- [#7665](https://github.com/blockscout/blockscout/pull/7665) - Add standard-json vyper verification
+- [#7685](https://github.com/blockscout/blockscout/pull/7685) - Add yul filter and "language" field for smart contracts
+- [#7653](https://github.com/blockscout/blockscout/pull/7653) - Add support for DEPOSIT and WITHDRAW token transfer event in older contracts
+- [#7628](https://github.com/blockscout/blockscout/pull/7628) - Support partially verified property from verifier MS; Add property to track contracts automatically verified via eth-bytecode-db
+- [#7603](https://github.com/blockscout/blockscout/pull/7603) - Add Polygon Edge and optimism genesis files support
+- [#7585](https://github.com/blockscout/blockscout/pull/7585) - Store and display native coin market cap from the DB
+- [#7513](https://github.com/blockscout/blockscout/pull/7513) - Add Polygon Edge support
- [#7532](https://github.com/blockscout/blockscout/pull/7532) - Handle empty id in json rpc responses
+- [#7544](https://github.com/blockscout/blockscout/pull/7544) - Add ERC-1155 signatures to uncataloged_token_transfer_block_numbers
+- [#7363](https://github.com/blockscout/blockscout/pull/7363) - CSV export filters
+- [#7697](https://github.com/blockscout/blockscout/pull/7697) - Limit fetchers init tasks
### Fixes
+- [#7712](https://github.com/blockscout/blockscout/pull/7712) - Transaction actions import fix
+- [#7709](https://github.com/blockscout/blockscout/pull/7709) - Contract args displaying bug
+- [#7654](https://github.com/blockscout/blockscout/pull/7654) - Optimize exchange rates requests rate
+- [#7636](https://github.com/blockscout/blockscout/pull/7636) - Remove receive from read methods
+- [#7635](https://github.com/blockscout/blockscout/pull/7635) - Fix single 1155 transfer displaying
+- [#7629](https://github.com/blockscout/blockscout/pull/7629) - Fix NFT fetcher
+- [#7614](https://github.com/blockscout/blockscout/pull/7614) - API and smart-contracts fixes and improvements
+- [#7611](https://github.com/blockscout/blockscout/pull/7611) - Fix tokens pagination
+- [#7566](https://github.com/blockscout/blockscout/pull/7566) - Account: check composed email before sending
+- [#7564](https://github.com/blockscout/blockscout/pull/7564) - Return contract type in address view
+- [#7562](https://github.com/blockscout/blockscout/pull/7562) - Remove fallback from Read methods
+- [#7537](https://github.com/blockscout/blockscout/pull/7537), [#7553](https://github.com/blockscout/blockscout/pull/7553) - Withdrawals fixes and improvements
+- [#7546](https://github.com/blockscout/blockscout/pull/7546) - API v2: fix today coin price (use in-memory or cached in DB value)
+- [#7545](https://github.com/blockscout/blockscout/pull/7545) - API v2: Check if cached exchange rate is empty before replacing DB value in stats API
- [#7516](https://github.com/blockscout/blockscout/pull/7516) - Fix shrinking logo in Safari
+- [#7590](https://github.com/blockscout/blockscout/pull/7590) - Drop genesis block in internal transactions fetcher
+- [#7639](https://github.com/blockscout/blockscout/pull/7639) - Fix contract creation transactions
+- [#7724](https://github.com/blockscout/blockscout/pull/7724), [#7753](https://github.com/blockscout/blockscout/pull/7753) - Move MissingRangesCollector init logic to handle_continue
+- [#7751](https://github.com/blockscout/blockscout/pull/7751) - Add missing method_to_url params for trace transactions
### Chore
+- [#7699](https://github.com/blockscout/blockscout/pull/7699) - Add block_number index for address_coin_balances table
+- [#7666](https://github.com/blockscout/blockscout/pull/7666), [#7740](https://github.com/blockscout/blockscout/pull/7740), [#7741](https://github.com/blockscout/blockscout/pull/7741) - Search label query
+- [#7644](https://github.com/blockscout/blockscout/pull/7644) - Publish docker images CI for prod/staging branches
+- [#7594](https://github.com/blockscout/blockscout/pull/7594) - Stats service support in docker-compose config with new frontend
+- [#7576](https://github.com/blockscout/blockscout/pull/7576) - Check left blocks in pending block operations in order to decide, if we need to display indexing int tx banner at the top
+- [#7543](https://github.com/blockscout/blockscout/pull/7543) - Allow hyphen in DB username
+
Dependencies version bumps
+- [#7518](https://github.com/blockscout/blockscout/pull/7518) - Bump mini-css-extract-plugin from 2.7.5 to 2.7.6 in /apps/block_scout_web/assets
+- [#7519](https://github.com/blockscout/blockscout/pull/7519) - Bump style-loader from 3.3.2 to 3.3.3 in /apps/block_scout_web/assets
+- [#7505](https://github.com/blockscout/blockscout/pull/7505) - Bump webpack from 5.83.0 to 5.83.1 in /apps/block_scout_web/assets
+- [#7533](https://github.com/blockscout/blockscout/pull/7533) - Bump sass-loader from 13.2.2 to 13.3.0 in /apps/block_scout_web/assets
+- [#7534](https://github.com/blockscout/blockscout/pull/7534) - Bump eslint from 8.40.0 to 8.41.0 in /apps/block_scout_web/assets
+- [#7541](https://github.com/blockscout/blockscout/pull/7541) - Bump cldr_utils from 2.23.1 to 2.24.0
+- [#7542](https://github.com/blockscout/blockscout/pull/7542) - Bump ex_cldr_units from 3.16.0 to 3.16.1
+- [#7548](https://github.com/blockscout/blockscout/pull/7548) - Bump briefly from 20d1318 to 678a376
+- [#7547](https://github.com/blockscout/blockscout/pull/7547) - Bump webpack from 5.83.1 to 5.84.0 in /apps/block_scout_web/assets
+- [#7554](https://github.com/blockscout/blockscout/pull/7554) - Bump webpack from 5.84.0 to 5.84.1 in /apps/block_scout_web/assets
+- [#7568](https://github.com/blockscout/blockscout/pull/7568) - Bump @babel/core from 7.21.8 to 7.22.1 in /apps/block_scout_web/assets
+- [#7569](https://github.com/blockscout/blockscout/pull/7569) - Bump postcss-loader from 7.3.0 to 7.3.1 in /apps/block_scout_web/assets
+- [#7570](https://github.com/blockscout/blockscout/pull/7570) - Bump number from 1.0.3 to 1.0.4
+- [#7567](https://github.com/blockscout/blockscout/pull/7567) - Bump @babel/preset-env from 7.21.5 to 7.22.2 in /apps/block_scout_web/assets
+- [#7582](https://github.com/blockscout/blockscout/pull/7582) - Bump eslint-config-standard from 17.0.0 to 17.1.0 in /apps/block_scout_web/assets
+- [#7581](https://github.com/blockscout/blockscout/pull/7581) - Bump sass-loader from 13.3.0 to 13.3.1 in /apps/block_scout_web/assets
+- [#7578](https://github.com/blockscout/blockscout/pull/7578) - Bump @babel/preset-env from 7.22.2 to 7.22.4 in /apps/block_scout_web/assets
+- [#7577](https://github.com/blockscout/blockscout/pull/7577) - Bump postcss-loader from 7.3.1 to 7.3.2 in /apps/block_scout_web/assets
+- [#7579](https://github.com/blockscout/blockscout/pull/7579) - Bump sweetalert2 from 11.7.5 to 11.7.8 in /apps/block_scout_web/assets
+- [#7591](https://github.com/blockscout/blockscout/pull/7591) - Bump sweetalert2 from 11.7.8 to 11.7.9 in /apps/block_scout_web/assets
+- [#7593](https://github.com/blockscout/blockscout/pull/7593) - Bump ex_json_schema from 0.9.2 to 0.9.3
+- [#7580](https://github.com/blockscout/blockscout/pull/7580) - Bump postcss from 8.4.23 to 8.4.24 in /apps/block_scout_web/assets
+- [#7601](https://github.com/blockscout/blockscout/pull/7601) - Bump sweetalert2 from 11.7.9 to 11.7.10 in /apps/block_scout_web/assets
+- [#7602](https://github.com/blockscout/blockscout/pull/7602) - Bump mime from 2.0.3 to 2.0.4
+- [#7618](https://github.com/blockscout/blockscout/pull/7618) - Bump gettext from 0.22.1 to 0.22.2
+- [#7617](https://github.com/blockscout/blockscout/pull/7617) - Bump @amplitude/analytics-browser from 1.10.3 to 1.10.4 in /apps/block_scout_web/assets
+- [#7609](https://github.com/blockscout/blockscout/pull/7609) - Bump webpack from 5.84.1 to 5.85.0 in /apps/block_scout_web/assets
+- [#7610](https://github.com/blockscout/blockscout/pull/7610) - Bump mime from 2.0.4 to 2.0.5
+- [#7634](https://github.com/blockscout/blockscout/pull/7634) - Bump eslint from 8.41.0 to 8.42.0 in /apps/block_scout_web/assets
+- [#7633](https://github.com/blockscout/blockscout/pull/7633) - Bump floki from 0.34.2 to 0.34.3
+- [#7631](https://github.com/blockscout/blockscout/pull/7631) - Bump phoenix_ecto from 4.4.1 to 4.4.2
+- [#7630](https://github.com/blockscout/blockscout/pull/7630) - Bump webpack-cli from 5.1.1 to 5.1.3 in /apps/block_scout_web/assets
+- [#7632](https://github.com/blockscout/blockscout/pull/7632) - Bump webpack from 5.85.0 to 5.85.1 in /apps/block_scout_web/assets
+- [#7646](https://github.com/blockscout/blockscout/pull/7646) - Bump sweetalert2 from 11.7.10 to 11.7.11 in /apps/block_scout_web/assets
+- [#7647](https://github.com/blockscout/blockscout/pull/7647) - Bump @amplitude/analytics-browser from 1.10.4 to 1.10.6 in /apps/block_scout_web/assets
+- [#7659](https://github.com/blockscout/blockscout/pull/7659) - Bump webpack-cli from 5.1.3 to 5.1.4 in /apps/block_scout_web/assets
+- [#7658](https://github.com/blockscout/blockscout/pull/7658) - Bump @amplitude/analytics-browser from 1.10.6 to 1.10.7 in /apps/block_scout_web/assets
+- [#7657](https://github.com/blockscout/blockscout/pull/7657) - Bump webpack from 5.85.1 to 5.86.0 in /apps/block_scout_web/assets
+- [#7672](https://github.com/blockscout/blockscout/pull/7672) - Bump @babel/preset-env from 7.22.4 to 7.22.5 in /apps/block_scout_web/assets
+- [#7674](https://github.com/blockscout/blockscout/pull/7674) - Bump ecto from 3.10.1 to 3.10.2
+- [#7673](https://github.com/blockscout/blockscout/pull/7673) - Bump @babel/core from 7.22.1 to 7.22.5 in /apps/block_scout_web/assets
+- [#7671](https://github.com/blockscout/blockscout/pull/7671) - Bump sass from 1.62.1 to 1.63.2 in /apps/block_scout_web/assets
+- [#7681](https://github.com/blockscout/blockscout/pull/7681) - Bump sweetalert2 from 11.7.11 to 11.7.12 in /apps/block_scout_web/assets
+- [#7679](https://github.com/blockscout/blockscout/pull/7679) - Bump @amplitude/analytics-browser from 1.10.7 to 1.10.8 in /apps/block_scout_web/assets
+- [#7680](https://github.com/blockscout/blockscout/pull/7680) - Bump sass from 1.63.2 to 1.63.3 in /apps/block_scout_web/assets
+- [#7693](https://github.com/blockscout/blockscout/pull/7693) - Bump sass-loader from 13.3.1 to 13.3.2 in /apps/block_scout_web/assets
+- [#7692](https://github.com/blockscout/blockscout/pull/7692) - Bump postcss-loader from 7.3.2 to 7.3.3 in /apps/block_scout_web/assets
+- [#7691](https://github.com/blockscout/blockscout/pull/7691) - Bump url from 0.11.0 to 0.11.1 in /apps/block_scout_web/assets
+- [#7690](https://github.com/blockscout/blockscout/pull/7690) - Bump core-js from 3.30.2 to 3.31.0 in /apps/block_scout_web/assets
+- [#7701](https://github.com/blockscout/blockscout/pull/7701) - Bump css-minimizer-webpack-plugin from 5.0.0 to 5.0.1 in /apps/block_scout_web/assets
+- [#7702](https://github.com/blockscout/blockscout/pull/7702) - Bump @amplitude/analytics-browser from 1.10.8 to 1.11.0 in /apps/block_scout_web/assets
+- [#7708](https://github.com/blockscout/blockscout/pull/7708) - Bump phoenix_pubsub from 2.1.2 to 2.1.3
+- [#7707](https://github.com/blockscout/blockscout/pull/7707) - Bump @amplitude/analytics-browser from 1.11.0 to 2.0.0 in /apps/block_scout_web/assets
+- [#7706](https://github.com/blockscout/blockscout/pull/7706) - Bump webpack from 5.86.0 to 5.87.0 in /apps/block_scout_web/assets
+- [#7705](https://github.com/blockscout/blockscout/pull/7705) - Bump sass from 1.63.3 to 1.63.4 in /apps/block_scout_web/assets
+- [#7714](https://github.com/blockscout/blockscout/pull/7714) - Bump ex_cldr_units from 3.16.1 to 3.16.2
+- [#7748](https://github.com/blockscout/blockscout/pull/7748) - Bump mock from 0.3.7 to 0.3.8
+- [#7746](https://github.com/blockscout/blockscout/pull/7746) - Bump eslint from 8.42.0 to 8.43.0 in /apps/block_scout_web/assets
+- [#7747](https://github.com/blockscout/blockscout/pull/7747) - Bump cldr_utils from 2.24.0 to 2.24.1
+
## 5.1.5-beta
@@ -23,6 +756,7 @@
- [#7439](https://github.com/blockscout/blockscout/pull/7439) - Define batch size for token balance fetcher via runtime env var
- [#7298](https://github.com/blockscout/blockscout/pull/7298) - Add changes to support force email verification
+- [#7422](https://github.com/blockscout/blockscout/pull/7422) - Refactor state changes
- [#7416](https://github.com/blockscout/blockscout/pull/7416) - Add option to disable reCAPTCHA
- [#6694](https://github.com/blockscout/blockscout/pull/6694) - Add withdrawals support (EIP-4895)
- [#7355](https://github.com/blockscout/blockscout/pull/7355) - Add endpoint for token info import
diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md
deleted file mode 100644
index 1e5bff013634..000000000000
--- a/ISSUE_TEMPLATE.md
+++ /dev/null
@@ -1,22 +0,0 @@
-*Describe your issue here.*
-
-### Environment
-
-* Deployment type (Manual/Docker/Docker-compose):
-* Elixir & Erlang/OTP versions (`elixir -version`):
-* Node JS version (`node -v`):
-* Operating System:
-* Blockscout Version/branch/commit:
-* Archive node type && version (Erigon/Geth/Nethermind/Ganache/?):
-
-### Steps to reproduce
-
-*Tell us how to reproduce this issue. ❤️ if you can push up a branch to your fork with a regression test we can run to reproduce locally.*
-
-### Expected behaviour
-
-*Tell us what should happen.*
-
-### Actual behaviour
-
-*Tell us what happens instead.*
diff --git a/README.md b/README.md
index 316e101047f5..678d7f78d118 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
BlockScout
+Blockscout
Blockchain Explorer for inspecting and analyzing EVM Chains.
@@ -8,32 +8,31 @@
-BlockScout provides a comprehensive, easy-to-use interface for users to view, confirm, and inspect transactions on EVM (Ethereum Virtual Machine) blockchains. This includes the POA Network, Gnosis Chain, Ethereum Classic and other **Ethereum testnets, private networks and sidechains**.
+Blockscout provides a comprehensive, easy-to-use interface for users to view, confirm, and inspect transactions on EVM (Ethereum Virtual Machine) blockchains. This includes Ethereum Mainnet, Ethereum Classic, Optimism, Gnosis Chain and many other **Ethereum testnets, private networks, L2s and sidechains**.
See our [project documentation](https://docs.blockscout.com/) for detailed information and setup instructions.
-For questions, comments and feature requests see the [discussions section](https://github.com/blockscout/blockscout/discussions).
+For questions, comments and feature requests see the [discussions section](https://github.com/blockscout/blockscout/discussions) or via [Discord](https://discord.com/invite/blockscout).
-## About BlockScout
+## About Blockscout
-BlockScout is an Elixir application that allows users to search transactions, view accounts and balances, and verify smart contracts on the Ethereum network including all forks and sidechains.
+Blockscout allows users to search transactions, view accounts and balances, verify and interact with smart contracts and view and interact with applications on the Ethereum network including many forks, sidechains, L2s and testnets.
-Currently available full-featured block explorers (Etherscan, Etherchain, Blockchair) are closed systems which are not independently verifiable. As Ethereum sidechains continue to proliferate in both private and public settings, transparent, open-source tools are needed to analyze and validate transactions.
+Blockscout is an open-source alternative to centralized, closed source block explorers such as Etherscan, Etherchain and others. As Ethereum sidechains and L2s continue to proliferate in both private and public settings, transparent, open-source tools are needed to analyze and validate all transactions.
## Supported Projects
-BlockScout supports a number of projects. Hosted instances include POA Network, Gnosis Chain, Ethereum Classic, Sokol & Kovan testnets, and other EVM chains.
-
-- [List of hosted mainnets, testnets, and additional chains using BlockScout](https://docs.blockscout.com/for-projects/supported-projects)
-- [Hosted instance versions](https://docs.blockscout.com/about/use-cases/hosted-blockscout)
+Blockscout currently supports several hundred chains and rollups throughout the greater blockchain ecosystem. Ethereum, Cosmos, Polkadot, Avalanche, Near and many others include Blockscout integrations. [A comprehensive list is available here](https://docs.blockscout.com/about/projects). If your project is not listed, please submit a PR or [contact the team in Discord](https://discord.com/invite/blockscout).
## Getting Started
See the [project documentation](https://docs.blockscout.com/) for instructions:
-- [Requirements](https://docs.blockscout.com/for-developers/information-and-settings/requirements)
+- [Manual deployment](https://docs.blockscout.com/for-developers/deployment/manual-deployment-guide)
+- [Docker-compose deployment](https://docs.blockscout.com/for-developers/deployment/docker-compose-deployment)
+- [Kubernetes deployment](https://docs.blockscout.com/for-developers/deployment/kubernetes-deployment)
+- [Manual deployment (backend + old UI)](https://docs.blockscout.com/for-developers/deployment/manual-old-ui)
- [Ansible deployment](https://docs.blockscout.com/for-developers/ansible-deployment)
-- [Manual deployment](https://docs.blockscout.com/for-developers/manual-deployment)
- [ENV variables](https://docs.blockscout.com/for-developers/information-and-settings/env-variables)
- [Configuration options](https://docs.blockscout.com/for-developers/configuration-options)
diff --git a/apps/block_scout_web/assets/css/components/_transaction.scss b/apps/block_scout_web/assets/css/components/_transaction.scss
index a7c517a10847..8522b149c126 100644
--- a/apps/block_scout_web/assets/css/components/_transaction.scss
+++ b/apps/block_scout_web/assets/css/components/_transaction.scss
@@ -8,7 +8,7 @@
}
}
-.download-all-transactions {
+.download-all-items {
text-align: center;
color: #a3a9b5;
font-size: 13px;
@@ -16,7 +16,7 @@
@media (min-width: 768px) {
margin-top: 30px;
}
- .download-all-transactions-link {
+ .download-all-items-link {
display: inline-flex;
align-items: center;
text-decoration: none;
diff --git a/apps/block_scout_web/assets/css/theme/_dark-theme.scss b/apps/block_scout_web/assets/css/theme/_dark-theme.scss
index ceb33db9010c..7fe9c8d77fd5 100644
--- a/apps/block_scout_web/assets/css/theme/_dark-theme.scss
+++ b/apps/block_scout_web/assets/css/theme/_dark-theme.scss
@@ -413,7 +413,7 @@ $header-links-background-color-dark-mode: $dark-light !default;
}
// download csv button
- .download-all-transactions .download-all-transactions-link svg path {
+ .download-all-items .download-all-items-link svg path {
fill: $dark-primary;
}
diff --git a/apps/block_scout_web/assets/css/theme/custom_contracts/_circles-theme.scss b/apps/block_scout_web/assets/css/theme/custom_contracts/_circles-theme.scss
index 3ae71f538010..19416bf05e2a 100644
--- a/apps/block_scout_web/assets/css/theme/custom_contracts/_circles-theme.scss
+++ b/apps/block_scout_web/assets/css/theme/custom_contracts/_circles-theme.scss
@@ -272,7 +272,7 @@ $c-dark-text-color: #8a8dba;
}
// download csv button
- .download-all-transactions .download-all-transactions-link svg path {
+ .download-all-items .download-all-items-link svg path {
fill: $c-primary;
}
diff --git a/apps/block_scout_web/assets/css/theme/custom_contracts/_dark-forest-theme.scss b/apps/block_scout_web/assets/css/theme/custom_contracts/_dark-forest-theme.scss
index e008052008c8..96bc3bda8c57 100644
--- a/apps/block_scout_web/assets/css/theme/custom_contracts/_dark-forest-theme.scss
+++ b/apps/block_scout_web/assets/css/theme/custom_contracts/_dark-forest-theme.scss
@@ -424,7 +424,7 @@ $dark-primary-alternate: $dark-primary;
}
// download csv button
- .download-all-transactions .download-all-transactions-link svg path {
+ .download-all-items .download-all-items-link svg path {
fill: $dark-primary;
}
diff --git a/apps/block_scout_web/assets/js/lib/csv_download.js b/apps/block_scout_web/assets/js/lib/csv_download.js
index b315a837a83c..7f36a773e64b 100644
--- a/apps/block_scout_web/assets/js/lib/csv_download.js
+++ b/apps/block_scout_web/assets/js/lib/csv_download.js
@@ -38,10 +38,12 @@ $button.on('click', () => {
const addressHash = $button.data('address-hash')
const from = $('.js-datepicker-from').val()
const to = $('.js-datepicker-to').val()
+ const urlParams = new URLSearchParams(window.location.search)
+ const filterType = urlParams.get('filter_type')
+ const filterValue = urlParams.get('filter_value')
+ const baseURL = `${$button.data('link')}?address_id=${addressHash}&from_period=${from}&to_period=${to}&filter_type=${filterType}&filter_value=${filterValue}`
if (reCaptchaDisabled) {
- const url = `${$button.data('link')}?address_id=${addressHash}&from_period=${from}&to_period=${to}`
-
- download(url)
+ download(baseURL)
} else if (reCaptchaV3ClientKey) {
disableBtnWithSpinner()
// @ts-ignore
@@ -51,7 +53,7 @@ $button.on('click', () => {
// eslint-disable-next-line
grecaptcha.execute(reCaptchaV3ClientKey, { action: 'login' })
.then(function (token) {
- const url = `${$button.data('link')}?address_id=${addressHash}&from_period=${from}&to_period=${to}&recaptcha_response=${token}`
+ const url = `${baseURL}&recaptcha_response=${token}`
download(url)
})
@@ -62,7 +64,7 @@ $button.on('click', () => {
const recaptchaResponse = grecaptcha.getResponse()
if (recaptchaResponse) {
disableBtnWithSpinner()
- const url = `${$button.data('link')}?address_id=${addressHash}&from_period=${from}&to_period=${to}&recaptcha_response=${recaptchaResponse}`
+ const url = `${baseURL}&recaptcha_response=${recaptchaResponse}`
download(url, true, true)
}
@@ -88,7 +90,13 @@ $button.on('click', () => {
const downloadUrl = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = downloadUrl
- a.download = `${$button.data('type')}_for_${addressHash}_from_${from}_to_${to}.csv`
+ let fileName = `${$button.data('type')}_for_${addressHash}_from_${from}_to_${to}`
+ if (filterType && filterValue) {
+ fileName = `${fileName}_with_filter_type_${filterType}_value_${filterValue}.csv`
+ } else {
+ fileName = `${fileName}.csv`
+ }
+ a.download = fileName
document.body.appendChild(a)
a.click()
diff --git a/apps/block_scout_web/assets/js/lib/history_chart.js b/apps/block_scout_web/assets/js/lib/history_chart.js
index b8f451d72cba..d861602128aa 100644
--- a/apps/block_scout_web/assets/js/lib/history_chart.js
+++ b/apps/block_scout_web/assets/js/lib/history_chart.js
@@ -14,6 +14,7 @@ Chart.register(LineController, LineElement, PointElement, LinearScale, TimeScale
// @ts-ignore
const coinName = document.getElementById('js-coin-name').value
+// @ts-ignore
const chainId = document.getElementById('js-chain-id').value
const priceDataKey = `priceData${coinName}`
const txHistoryDataKey = `txHistoryData${coinName}${chainId}`
@@ -188,15 +189,12 @@ function getTxHistoryData (transactionHistory) {
return data
}
-function getMarketCapData (marketHistoryData, availableSupply) {
+function getMarketCapData (marketHistoryData) {
if (marketHistoryData.length === 0) {
return getDataFromLocalStorage(marketCapDataKey)
}
- const data = marketHistoryData.map(({ date, closingPrice }) => {
- const supply = (availableSupply !== null && typeof availableSupply === 'object')
- ? availableSupply[date]
- : availableSupply
- return { x: date, y: closingPrice * supply }
+ const data = marketHistoryData.map(({ date, marketCap }) => {
+ return { x: date, y: marketCap }
})
setDataToLocalStorage(marketCapDataKey, data)
return data
@@ -207,7 +205,7 @@ const priceLineColor = getPriceChartColor()
const mcapLineColor = getMarketCapChartColor()
class MarketHistoryChart {
- constructor (el, availableSupply, _marketHistoryData, dataConfig) {
+ constructor (el, _marketHistoryData, dataConfig) {
const axes = config.options.scales
let priceActivated = true
@@ -271,8 +269,6 @@ class MarketHistoryChart {
axes.numTransactions.position = 'left'
}
- this.availableSupply = availableSupply
-
const txChartTitle = 'Daily transactions'
const marketChartTitle = 'Daily price and market cap'
let chartTitle = ''
@@ -297,15 +293,9 @@ class MarketHistoryChart {
this.chart = new Chart(el, config)
}
- updateMarketHistory (availableSupply, marketHistoryData) {
+ updateMarketHistory (marketHistoryData) {
this.price.data = getPriceData(marketHistoryData)
- if (this.availableSupply !== null && typeof this.availableSupply === 'object') {
- const today = new Date().toJSON().slice(0, 10)
- this.availableSupply[today] = availableSupply
- this.marketCap.data = getMarketCapData(marketHistoryData, this.availableSupply)
- } else {
- this.marketCap.data = getMarketCapData(marketHistoryData, availableSupply)
- }
+ this.marketCap.data = getMarketCapData(marketHistoryData)
this.chart.update()
}
@@ -320,17 +310,16 @@ export function createMarketHistoryChart (el) {
const dataConfig = $(el).data('history_chart_config')
const $chartError = $('[data-chart-error-message]')
- const chart = new MarketHistoryChart(el, 0, [], dataConfig)
+ const chart = new MarketHistoryChart(el, [], dataConfig)
Object.keys(dataPaths).forEach(function (historySource) {
$.getJSON(dataPaths[historySource], { type: 'JSON' })
.done(data => {
switch (historySource) {
case 'market': {
- const availableSupply = JSON.parse(data.supply_data)
- const marketHistoryData = humps.camelizeKeys(JSON.parse(data.history_data))
+ const marketHistoryData = humps.camelizeKeys(data.history_data)
$(el).show()
- chart.updateMarketHistory(availableSupply, marketHistoryData)
+ chart.updateMarketHistory(marketHistoryData)
break
}
case 'transaction': {
diff --git a/apps/block_scout_web/assets/js/pages/address/logs.js b/apps/block_scout_web/assets/js/pages/address/logs.js
index 2997f53f3b92..e12e37c87475 100644
--- a/apps/block_scout_web/assets/js/pages/address/logs.js
+++ b/apps/block_scout_web/assets/js/pages/address/logs.js
@@ -3,6 +3,7 @@ import omit from 'lodash.omit'
import { connectElements } from '../../lib/redux_helpers.js'
import { createAsyncLoadStore, loadPage } from '../../lib/async_listing_load'
import { commonPath } from '../../lib/path_helper'
+import { escapeHtml } from '../../lib/utils'
import '../address'
// @ts-ignore
import { utils } from 'web3'
@@ -76,9 +77,22 @@ if ($('[data-page="address-logs"]').length) {
const addressHashPlain = store.getState().addressHash
const addressHashChecksum = addressHashPlain && utils.toChecksumAddress(addressHashPlain)
const path = `${commonPath}/search-logs?topic=${topic}&address_id=${addressHashChecksum}`
+ changeDownloadButtonHref(topic)
loadPage(store, path)
}
+ function changeDownloadButtonHref (filter) {
+ const currentHref = $('a.download-all-items-link').attr('href')
+ if (currentHref) {
+ let hrefWithTopic = currentHref
+ if (currentHref.includes('filter_type=&')) {
+ hrefWithTopic = currentHref.replace(/filter_type=.*?&/, 'filter_type=topic&')
+ }
+ const href = hrefWithTopic.replace(/filter_value=.*?&/, `filter_value=${escapeHtml(filter)}&`)
+ $('a.download-all-items-link').attr('href', href)
+ }
+ }
+
store.dispatch({
type: 'PAGE_LOAD',
addressHash
diff --git a/apps/block_scout_web/assets/js/pages/verification_form.js b/apps/block_scout_web/assets/js/pages/verification_form.js
index ff3db1b9e241..0b546bf3eee2 100644
--- a/apps/block_scout_web/assets/js/pages/verification_form.js
+++ b/apps/block_scout_web/assets/js/pages/verification_form.js
@@ -127,9 +127,7 @@ if ($contractVerificationPage.length) {
$(function () {
initializeDropzone()
- setTimeout(function () {
- $('.nightly-builds-false').trigger('click')
- }, 10)
+ filterNightlyBuilds(true, false)
$('body').on('click', '.js-btn-add-contract-libraries', function () {
$('.js-smart-contract-libraries-wrapper').show()
diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json
index bba41766e553..49fded9ff7b5 100644
--- a/apps/block_scout_web/assets/package-lock.json
+++ b/apps/block_scout_web/assets/package-lock.json
@@ -7,24 +7,24 @@
"name": "blockscout",
"license": "GPL-3.0",
"dependencies": {
- "@amplitude/analytics-browser": "^1.10.3",
- "@fortawesome/fontawesome-free": "^6.4.0",
+ "@amplitude/analytics-browser": "^2.3.8",
+ "@fortawesome/fontawesome-free": "^6.5.1",
"@tarekraafat/autocomplete.js": "^10.2.7",
"@walletconnect/web3-provider": "^1.8.0",
- "assert": "^2.0.0",
- "bignumber.js": "^9.1.1",
+ "assert": "^2.1.0",
+ "bignumber.js": "^9.1.2",
"bootstrap": "^4.6.0",
- "chart.js": "^4.3.0",
+ "chart.js": "^4.4.1",
"chartjs-adapter-luxon": "^1.3.1",
"clipboard": "^2.0.11",
- "core-js": "^3.30.2",
+ "core-js": "^3.35.0",
"crypto-browserify": "^3.12.0",
"dropzone": "^5.9.3",
"eth-net-props": "^1.0.41",
- "highlight.js": "^11.8.0",
+ "highlight.js": "^11.9.0",
"https-browserify": "^1.0.0",
"humps": "^2.0.1",
- "jquery": "^3.7.0",
+ "jquery": "^3.7.1",
"js-cookie": "^3.0.5",
"lodash.debounce": "^4.0.8",
"lodash.differenceby": "^4.8.0",
@@ -44,56 +44,56 @@
"lodash.omit": "^4.5.0",
"lodash.rangeright": "^4.2.0",
"lodash.reduce": "^4.6.0",
- "luxon": "^3.3.0",
+ "luxon": "^3.4.4",
"malihu-custom-scrollbar-plugin": "3.1.5",
- "mixpanel-browser": "^2.47.0",
- "moment": "^2.29.4",
+ "mixpanel-browser": "^2.48.1",
+ "moment": "^2.30.1",
"nanomorph": "^5.4.0",
"numeral": "^2.0.6",
"os-browserify": "^0.3.0",
"path-parser": "^6.1.0",
"phoenix": "file:../../../deps/phoenix",
"phoenix_html": "file:../../../deps/phoenix_html",
- "photoswipe": "^5.3.7",
+ "photoswipe": "^5.4.3",
"pikaday": "^1.8.2",
"popper.js": "^1.14.7",
"reduce-reducers": "^1.0.4",
- "redux": "^4.2.1",
+ "redux": "^5.0.1",
"stream-browserify": "^3.0.0",
"stream-http": "^3.1.1",
- "sweetalert2": "^11.7.5",
+ "sweetalert2": "^11.10.3",
"urijs": "^1.19.11",
- "url": "^0.11.0",
+ "url": "^0.11.3",
"util": "^0.12.5",
- "viewerjs": "^1.11.3",
- "web3": "^1.10.0",
+ "viewerjs": "^1.11.6",
+ "web3": "^1.10.3",
"web3modal": "^1.9.12",
"xss": "^1.0.14"
},
"devDependencies": {
- "@babel/core": "^7.21.8",
- "@babel/preset-env": "^7.21.5",
- "autoprefixer": "^10.4.14",
- "babel-loader": "^9.1.2",
- "copy-webpack-plugin": "^11.0.0",
- "css-loader": "^5.2.7",
- "css-minimizer-webpack-plugin": "^5.0.0",
- "eslint": "^8.41.0",
- "eslint-config-standard": "^17.0.0",
- "eslint-plugin-import": "^2.27.5",
+ "@babel/core": "^7.23.7",
+ "@babel/preset-env": "^7.23.8",
+ "autoprefixer": "^10.4.16",
+ "babel-loader": "^9.1.3",
+ "copy-webpack-plugin": "^12.0.1",
+ "css-loader": "^6.9.0",
+ "css-minimizer-webpack-plugin": "^5.0.1",
+ "eslint": "^8.56.0",
+ "eslint-config-standard": "^17.1.0",
+ "eslint-plugin-import": "^2.29.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.1.1",
"file-loader": "^6.2.0",
- "jest": "^29.5.0",
- "jest-environment-jsdom": "^29.5.0",
- "mini-css-extract-plugin": "^2.7.6",
- "postcss": "^8.4.23",
- "postcss-loader": "^7.3.0",
- "sass": "^1.62.1",
- "sass-loader": "^13.3.0",
- "style-loader": "^3.3.3",
- "webpack": "^5.83.1",
- "webpack-cli": "^5.1.1"
+ "jest": "^29.7.0",
+ "jest-environment-jsdom": "^29.7.0",
+ "mini-css-extract-plugin": "^2.7.7",
+ "postcss": "^8.4.33",
+ "postcss-loader": "^7.3.4",
+ "sass": "^1.69.7",
+ "sass-loader": "^14.0.0",
+ "style-loader": "^3.3.4",
+ "webpack": "^5.89.0",
+ "webpack-cli": "^5.1.4"
},
"engines": {
"node": ">=16.0.0",
@@ -106,17 +106,25 @@
"../../../deps/phoenix_html": {
"version": "0.0.1"
},
+ "node_modules/@aashutoshrathi/word-wrap": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
+ "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/@amplitude/analytics-browser": {
- "version": "1.10.3",
- "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-1.10.3.tgz",
- "integrity": "sha512-u9xnHVLMtoWawZssZDC1wYR7GjuXF9DWL09fCdxf5j0Gp9CtRurzt+kUx9KC8IzrBmNSYZ7jG8xhwUWCbIFodg==",
- "dependencies": {
- "@amplitude/analytics-client-common": "^0.7.0",
- "@amplitude/analytics-core": "^0.13.3",
- "@amplitude/analytics-types": "^0.20.0",
- "@amplitude/plugin-page-view-tracking-browser": "^0.8.0",
- "@amplitude/plugin-web-attribution-browser": "^0.7.0",
- "@amplitude/ua-parser-js": "^0.7.31",
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.8.tgz",
+ "integrity": "sha512-K+12aAVJPzAtWIi8Ok5Q5dvg7v7IF4G0cI8PW0COWo3uTyY103r45OcpgrpRVpVAr+41d1eiMo36jqOke89uPA==",
+ "dependencies": {
+ "@amplitude/analytics-client-common": "^2.0.10",
+ "@amplitude/analytics-core": "^2.1.3",
+ "@amplitude/analytics-types": "^2.3.1",
+ "@amplitude/plugin-page-view-tracking-browser": "^2.0.18",
+ "@amplitude/plugin-web-attribution-browser": "^2.0.18",
"tslib": "^2.4.1"
}
},
@@ -126,95 +134,75 @@
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
},
"node_modules/@amplitude/analytics-client-common": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-0.7.0.tgz",
- "integrity": "sha512-fKBR5DSo6qTqFcCFnLopRdWv6Zieox3YnzyXG2Zg1hR9MDeX96GoMEwwT2VB5MPzGxE1wYW6kQPvU2C/xpn5ww==",
+ "version": "2.0.10",
+ "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.10.tgz",
+ "integrity": "sha512-IaERGgBN3dmCGFbFd7SFTpTBguJIQzE/uDK44KEnLj0qw9wdoTxpLhoXQpqe5WKWsr46eONL9ROCJybHs4Efnw==",
"dependencies": {
- "@amplitude/analytics-connector": "^1.4.5",
- "@amplitude/analytics-core": "^0.13.3",
- "@amplitude/analytics-types": "^0.20.0",
+ "@amplitude/analytics-connector": "^1.4.8",
+ "@amplitude/analytics-core": "^2.1.3",
+ "@amplitude/analytics-types": "^2.3.1",
"tslib": "^2.4.1"
}
},
"node_modules/@amplitude/analytics-client-common/node_modules/tslib": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
- "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"node_modules/@amplitude/analytics-connector": {
- "version": "1.4.7",
- "resolved": "https://registry.npmjs.org/@amplitude/analytics-connector/-/analytics-connector-1.4.7.tgz",
- "integrity": "sha512-XjkFmVnJuPcRAQyPDs3LR3uam0jjDzBsT8ZABlWoV3iypRY7d+gs3ijDn2c4UU5XIjsR9GqzHCbZG3w7T+t6tQ==",
- "dependencies": {
- "@amplitude/ua-parser-js": "^0.7.31"
- }
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@amplitude/analytics-connector/-/analytics-connector-1.5.0.tgz",
+ "integrity": "sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g=="
},
"node_modules/@amplitude/analytics-core": {
- "version": "0.13.3",
- "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-0.13.3.tgz",
- "integrity": "sha512-lLLmUwi78iqGsCPgd15yH488K+3FPYQ19fCdA3OemoEK9959xCzMZe/KYafQKkgZmI4ZiZLOp3pjamKWc6mcdQ==",
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.3.tgz",
+ "integrity": "sha512-WHXf9g33t63jYy4a/1uOpq/zHPMfEj5N2HHgJrg7Eu7v4w3kOWtPSMPBAllzFWxC5Ay5HeR9n0hqlJG0yffQWg==",
"dependencies": {
- "@amplitude/analytics-types": "^0.20.0",
+ "@amplitude/analytics-types": "^2.3.1",
"tslib": "^2.4.1"
}
},
"node_modules/@amplitude/analytics-core/node_modules/tslib": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
- "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"node_modules/@amplitude/analytics-types": {
- "version": "0.20.0",
- "resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-0.20.0.tgz",
- "integrity": "sha512-OYoUrf7QUl5sL4mqcQJBu+0GuSar7ga5prJv0HDtPT4LGu7Adv8S3AWljGiEJuTS/YaOMzvTe2qyKZbwyiNrMw=="
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-2.3.1.tgz",
+ "integrity": "sha512-yojBG20qvph0rpCJKb4i/FJa+otqLINEwv//hfzvjnCOcPPyS0YscI8oiRBM0rG7kZIDgaL9a6jPwkqK4ACmcw=="
},
"node_modules/@amplitude/plugin-page-view-tracking-browser": {
- "version": "0.8.0",
- "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-0.8.0.tgz",
- "integrity": "sha512-Bun7aGo7woxsnupDEY1YugLQyAXDRtFhQsXnJKKCIkyeeRCq4qsuaAz0wI8w1QV+3IhaAf0lBuSqSuBIhPFDLQ==",
+ "version": "2.0.18",
+ "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.18.tgz",
+ "integrity": "sha512-s7PkGOgrx6U06/emzM8k+KRGDuyP9Z2L4OyGdeQwJcURJjiZDVQsmKlTZ5/SeGvxHYgq/4QJYUmMSmzByiGTCA==",
"dependencies": {
- "@amplitude/analytics-client-common": "^0.7.0",
- "@amplitude/analytics-types": "^0.20.0",
+ "@amplitude/analytics-client-common": "^2.0.10",
+ "@amplitude/analytics-types": "^2.3.1",
"tslib": "^2.4.1"
}
},
"node_modules/@amplitude/plugin-page-view-tracking-browser/node_modules/tslib": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
- "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"node_modules/@amplitude/plugin-web-attribution-browser": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-0.7.0.tgz",
- "integrity": "sha512-PKC6SHIVGXvxhxO4CZQOYidoo+aKApLRFGBbRfDEVdSxEDY1b1wTHtqTJomxodqvw4pLocSDiuwC1s7B0p4zAQ==",
+ "version": "2.0.18",
+ "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.18.tgz",
+ "integrity": "sha512-mPlXu0fEYCCXhT6WpNJoM0FYkp+HIEm7L+KBEa2IHd3GD3+mh2AVDkZmgXLl3LKb++HY8mCiqC5/NcJ2AzTNhA==",
"dependencies": {
- "@amplitude/analytics-client-common": "^0.7.0",
- "@amplitude/analytics-types": "^0.20.0",
+ "@amplitude/analytics-client-common": "^2.0.10",
+ "@amplitude/analytics-core": "^2.1.3",
+ "@amplitude/analytics-types": "^2.3.1",
"tslib": "^2.4.1"
}
},
"node_modules/@amplitude/plugin-web-attribution-browser/node_modules/tslib": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
- "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
- },
- "node_modules/@amplitude/ua-parser-js": {
- "version": "0.7.33",
- "resolved": "https://registry.npmjs.org/@amplitude/ua-parser-js/-/ua-parser-js-0.7.33.tgz",
- "integrity": "sha512-wKEtVR4vXuPT9cVEIJkYWnlF++Gx3BdLatPBM+SZ1ztVIvnhdGBZR/mn9x/PzyrMcRlZmyi6L56I2J3doVBnjA==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/ua-parser-js"
- },
- {
- "type": "paypal",
- "url": "https://paypal.me/faisalman"
- }
- ],
- "engines": {
- "node": "*"
- }
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"node_modules/@ampproject/remapping": {
"version": "2.2.0",
@@ -241,44 +229,45 @@
}
},
"node_modules/@babel/code-frame": {
- "version": "7.21.4",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz",
- "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==",
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
+ "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
"dependencies": {
- "@babel/highlight": "^7.18.6"
+ "@babel/highlight": "^7.23.4",
+ "chalk": "^2.4.2"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/compat-data": {
- "version": "7.21.7",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.7.tgz",
- "integrity": "sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA==",
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz",
+ "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
- "version": "7.21.8",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.8.tgz",
- "integrity": "sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==",
+ "version": "7.23.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz",
+ "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
- "@babel/code-frame": "^7.21.4",
- "@babel/generator": "^7.21.5",
- "@babel/helper-compilation-targets": "^7.21.5",
- "@babel/helper-module-transforms": "^7.21.5",
- "@babel/helpers": "^7.21.5",
- "@babel/parser": "^7.21.8",
- "@babel/template": "^7.20.7",
- "@babel/traverse": "^7.21.5",
- "@babel/types": "^7.21.5",
- "convert-source-map": "^1.7.0",
+ "@babel/code-frame": "^7.23.5",
+ "@babel/generator": "^7.23.6",
+ "@babel/helper-compilation-targets": "^7.23.6",
+ "@babel/helper-module-transforms": "^7.23.3",
+ "@babel/helpers": "^7.23.7",
+ "@babel/parser": "^7.23.6",
+ "@babel/template": "^7.22.15",
+ "@babel/traverse": "^7.23.7",
+ "@babel/types": "^7.23.6",
+ "convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
- "json5": "^2.2.2",
- "semver": "^6.3.0"
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
@@ -288,12 +277,17 @@
"url": "https://opencollective.com/babel"
}
},
+ "node_modules/@babel/core/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=="
+ },
"node_modules/@babel/generator": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.5.tgz",
- "integrity": "sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==",
+ "version": "7.23.6",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz",
+ "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==",
"dependencies": {
- "@babel/types": "^7.21.5",
+ "@babel/types": "^7.23.6",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
@@ -303,45 +297,41 @@
}
},
"node_modules/@babel/helper-annotate-as-pure": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz",
- "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==",
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz",
+ "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==",
"dependencies": {
- "@babel/types": "^7.18.6"
+ "@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-builder-binary-assignment-operator-visitor": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz",
- "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz",
+ "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==",
"dev": true,
"dependencies": {
- "@babel/helper-explode-assignable-expression": "^7.18.6",
- "@babel/types": "^7.18.9"
+ "@babel/types": "^7.22.15"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-compilation-targets": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz",
- "integrity": "sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==",
+ "version": "7.23.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz",
+ "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==",
"dependencies": {
- "@babel/compat-data": "^7.21.5",
- "@babel/helper-validator-option": "^7.21.0",
- "browserslist": "^4.21.3",
+ "@babel/compat-data": "^7.23.5",
+ "@babel/helper-validator-option": "^7.23.5",
+ "browserslist": "^4.22.2",
"lru-cache": "^5.1.1",
- "semver": "^6.3.0"
+ "semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
@@ -358,19 +348,20 @@
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
},
"node_modules/@babel/helper-create-class-features-plugin": {
- "version": "7.21.4",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.4.tgz",
- "integrity": "sha512-46QrX2CQlaFRF4TkwfTt6nJD7IHq8539cCL7SDpqWSDeJKY1xylKKY5F/33mJhLZ3mFvKv2gGrVS6NkyF6qs+Q==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz",
+ "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==",
"dev": true,
"dependencies": {
- "@babel/helper-annotate-as-pure": "^7.18.6",
- "@babel/helper-environment-visitor": "^7.18.9",
- "@babel/helper-function-name": "^7.21.0",
- "@babel/helper-member-expression-to-functions": "^7.21.0",
- "@babel/helper-optimise-call-expression": "^7.18.6",
- "@babel/helper-replace-supers": "^7.20.7",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0",
- "@babel/helper-split-export-declaration": "^7.18.6"
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-function-name": "^7.22.5",
+ "@babel/helper-member-expression-to-functions": "^7.22.15",
+ "@babel/helper-optimise-call-expression": "^7.22.5",
+ "@babel/helper-replace-supers": "^7.22.9",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
@@ -380,13 +371,14 @@
}
},
"node_modules/@babel/helper-create-regexp-features-plugin": {
- "version": "7.21.4",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.4.tgz",
- "integrity": "sha512-M00OuhU+0GyZ5iBBN9czjugzWrEq2vDpf/zCYHxxf93ul/Q5rv+a5h+/+0WnI1AebHNVtl5bFV0qsJoH23DbfA==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz",
+ "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==",
"dev": true,
"dependencies": {
- "@babel/helper-annotate-as-pure": "^7.18.6",
- "regexpu-core": "^5.3.1"
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "regexpu-core": "^5.3.1",
+ "semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
@@ -412,119 +404,106 @@
}
},
"node_modules/@babel/helper-environment-visitor": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz",
- "integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-explode-assignable-expression": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz",
- "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==",
- "dev": true,
- "dependencies": {
- "@babel/types": "^7.18.6"
- },
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
+ "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-function-name": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz",
- "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==",
+ "version": "7.23.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
+ "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
"dependencies": {
- "@babel/template": "^7.20.7",
- "@babel/types": "^7.21.0"
+ "@babel/template": "^7.22.15",
+ "@babel/types": "^7.23.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-hoist-variables": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
- "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
+ "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
"dependencies": {
- "@babel/types": "^7.18.6"
+ "@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-member-expression-to-functions": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz",
- "integrity": "sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q==",
+ "version": "7.23.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz",
+ "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==",
"dev": true,
"dependencies": {
- "@babel/types": "^7.21.0"
+ "@babel/types": "^7.23.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-imports": {
- "version": "7.21.4",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz",
- "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
+ "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
"dependencies": {
- "@babel/types": "^7.21.4"
+ "@babel/types": "^7.22.15"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz",
- "integrity": "sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz",
+ "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==",
"dependencies": {
- "@babel/helper-environment-visitor": "^7.21.5",
- "@babel/helper-module-imports": "^7.21.4",
- "@babel/helper-simple-access": "^7.21.5",
- "@babel/helper-split-export-declaration": "^7.18.6",
- "@babel/helper-validator-identifier": "^7.19.1",
- "@babel/template": "^7.20.7",
- "@babel/traverse": "^7.21.5",
- "@babel/types": "^7.21.5"
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-module-imports": "^7.22.15",
+ "@babel/helper-simple-access": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "@babel/helper-validator-identifier": "^7.22.20"
},
"engines": {
"node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-optimise-call-expression": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz",
- "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==",
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz",
+ "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==",
"dev": true,
"dependencies": {
- "@babel/types": "^7.18.6"
+ "@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-plugin-utils": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz",
- "integrity": "sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==",
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-remap-async-to-generator": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz",
- "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==",
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz",
+ "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==",
"dev": true,
"dependencies": {
- "@babel/helper-annotate-as-pure": "^7.18.6",
- "@babel/helper-environment-visitor": "^7.18.9",
- "@babel/helper-wrap-function": "^7.18.9",
- "@babel/types": "^7.18.9"
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-wrap-function": "^7.22.20"
},
"engines": {
"node": ">=6.9.0"
@@ -534,115 +513,114 @@
}
},
"node_modules/@babel/helper-replace-supers": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz",
- "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==",
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz",
+ "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==",
"dev": true,
"dependencies": {
- "@babel/helper-environment-visitor": "^7.18.9",
- "@babel/helper-member-expression-to-functions": "^7.20.7",
- "@babel/helper-optimise-call-expression": "^7.18.6",
- "@babel/template": "^7.20.7",
- "@babel/traverse": "^7.20.7",
- "@babel/types": "^7.20.7"
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-member-expression-to-functions": "^7.22.15",
+ "@babel/helper-optimise-call-expression": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-simple-access": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz",
- "integrity": "sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==",
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
+ "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
"dependencies": {
- "@babel/types": "^7.21.5"
+ "@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-skip-transparent-expression-wrappers": {
- "version": "7.20.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz",
- "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==",
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz",
+ "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==",
"dev": true,
"dependencies": {
- "@babel/types": "^7.20.0"
+ "@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-split-export-declaration": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
- "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
+ "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
"dependencies": {
- "@babel/types": "^7.18.6"
+ "@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz",
- "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
+ "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.19.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
- "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
+ "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-option": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz",
- "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==",
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz",
+ "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-wrap-function": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz",
- "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==",
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz",
+ "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==",
"dev": true,
"dependencies": {
- "@babel/helper-function-name": "^7.19.0",
- "@babel/template": "^7.18.10",
- "@babel/traverse": "^7.20.5",
- "@babel/types": "^7.20.5"
+ "@babel/helper-function-name": "^7.22.5",
+ "@babel/template": "^7.22.15",
+ "@babel/types": "^7.22.19"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.5.tgz",
- "integrity": "sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==",
+ "version": "7.23.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.7.tgz",
+ "integrity": "sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ==",
"dependencies": {
- "@babel/template": "^7.20.7",
- "@babel/traverse": "^7.21.5",
- "@babel/types": "^7.21.5"
+ "@babel/template": "^7.22.15",
+ "@babel/traverse": "^7.23.7",
+ "@babel/types": "^7.23.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
- "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
+ "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.18.6",
- "chalk": "^2.0.0",
+ "@babel/helper-validator-identifier": "^7.22.20",
+ "chalk": "^2.4.2",
"js-tokens": "^4.0.0"
},
"engines": {
@@ -650,9 +628,9 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.21.8",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.8.tgz",
- "integrity": "sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==",
+ "version": "7.23.6",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz",
+ "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==",
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -661,12 +639,12 @@
}
},
"node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
- "version": "7.18.6",
- "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.18.6.tgz",
- "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==",
+ "version": "7.23.3",
+ "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.23.3.tgz",
+ "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -676,14 +654,14 @@
}
},
"node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz",
- "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz",
+ "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0",
- "@babel/plugin-proposal-optional-chaining": "^7.20.7"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
+ "@babel/plugin-transform-optional-chaining": "^7.23.3"
},
"engines": {
"node": ">=6.9.0"
@@ -692,33 +670,27 @@
"@babel/core": "^7.13.0"
}
},
- "node_modules/@babel/plugin-proposal-async-generator-functions": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz",
- "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==",
+ "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": {
+ "version": "7.23.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz",
+ "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==",
"dev": true,
"dependencies": {
- "@babel/helper-environment-visitor": "^7.18.9",
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/helper-remap-async-to-generator": "^7.18.9",
- "@babel/plugin-syntax-async-generators": "^7.8.4"
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
- "@babel/core": "^7.0.0-0"
+ "@babel/core": "^7.0.0"
}
},
- "node_modules/@babel/plugin-proposal-class-properties": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz",
- "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==",
+ "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,
- "dependencies": {
- "@babel/helper-create-class-features-plugin": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.18.6"
- },
"engines": {
"node": ">=6.9.0"
},
@@ -726,63 +698,49 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-proposal-class-static-block": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz",
- "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==",
+ "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-create-class-features-plugin": "^7.21.0",
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/plugin-syntax-class-static-block": "^7.14.5"
- },
- "engines": {
- "node": ">=6.9.0"
+ "@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
- "@babel/core": "^7.12.0"
+ "@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-proposal-dynamic-import": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz",
- "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==",
+ "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.18.6",
- "@babel/plugin-syntax-dynamic-import": "^7.8.3"
- },
- "engines": {
- "node": ">=6.9.0"
+ "@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-proposal-export-namespace-from": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz",
- "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==",
+ "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.18.9",
- "@babel/plugin-syntax-export-namespace-from": "^7.8.3"
- },
- "engines": {
- "node": ">=6.9.0"
+ "@babel/helper-plugin-utils": "^7.12.13"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-proposal-json-strings": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz",
- "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==",
+ "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.18.6",
- "@babel/plugin-syntax-json-strings": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.14.5"
},
"engines": {
"node": ">=6.9.0"
@@ -791,46 +749,37 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-proposal-logical-assignment-operators": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz",
- "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==",
+ "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.20.2",
- "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4"
- },
- "engines": {
- "node": ">=6.9.0"
+ "@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz",
- "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==",
+ "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.18.6",
- "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3"
- },
- "engines": {
- "node": ">=6.9.0"
+ "@babel/helper-plugin-utils": "^7.8.3"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-proposal-numeric-separator": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz",
- "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==",
+ "node_modules/@babel/plugin-syntax-import-assertions": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz",
+ "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.18.6",
- "@babel/plugin-syntax-numeric-separator": "^7.10.4"
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -839,17 +788,13 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-proposal-object-rest-spread": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz",
- "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==",
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz",
+ "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==",
"dev": true,
"dependencies": {
- "@babel/compat-data": "^7.20.5",
- "@babel/helper-compilation-targets": "^7.20.7",
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
- "@babel/plugin-transform-parameters": "^7.20.7"
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -858,47 +803,37 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-proposal-optional-catch-binding": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz",
- "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==",
+ "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.18.6",
- "@babel/plugin-syntax-optional-catch-binding": "^7.8.3"
- },
- "engines": {
- "node": ">=6.9.0"
+ "@babel/helper-plugin-utils": "^7.10.4"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-proposal-optional-chaining": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz",
- "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==",
+ "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.20.2",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0",
- "@babel/plugin-syntax-optional-chaining": "^7.8.3"
- },
- "engines": {
- "node": ">=6.9.0"
+ "@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-proposal-private-methods": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz",
- "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==",
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz",
+ "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==",
"dev": true,
"dependencies": {
- "@babel/helper-create-class-features-plugin": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -907,44 +842,46 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-proposal-private-property-in-object": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz",
- "integrity": "sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==",
+ "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-annotate-as-pure": "^7.18.6",
- "@babel/helper-create-class-features-plugin": "^7.21.0",
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/plugin-syntax-private-property-in-object": "^7.14.5"
- },
- "engines": {
- "node": ">=6.9.0"
+ "@babel/helper-plugin-utils": "^7.10.4"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-proposal-unicode-property-regex": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz",
- "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==",
+ "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-create-regexp-features-plugin": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.8.0"
},
- "engines": {
- "node": ">=4"
+ "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-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==",
+ "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"
@@ -953,10 +890,10 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-syntax-bigint": {
+ "node_modules/@babel/plugin-syntax-optional-catch-binding": {
"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==",
+ "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"
@@ -965,22 +902,22 @@
"@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==",
+ "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.12.13"
+ "@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-syntax-class-static-block": {
+ "node_modules/@babel/plugin-syntax-private-property-in-object": {
"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==",
+ "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"
@@ -992,76 +929,94 @@
"@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==",
+ "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.8.0"
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.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==",
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz",
+ "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-syntax-import-assertions": {
- "version": "7.20.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz",
- "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==",
+ "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-plugin-utils": "^7.19.0"
+ "@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-0"
+ "@babel/core": "^7.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==",
+ "node_modules/@babel/plugin-transform-arrow-functions": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz",
+ "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.10.4"
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
},
"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==",
+ "node_modules/@babel/plugin-transform-async-generator-functions": {
+ "version": "7.23.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.7.tgz",
+ "integrity": "sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-remap-async-to-generator": "^7.22.20",
+ "@babel/plugin-syntax-async-generators": "^7.8.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-syntax-jsx": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz",
- "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==",
+ "node_modules/@babel/plugin-transform-async-to-generator": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz",
+ "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-module-imports": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-remap-async-to-generator": "^7.22.20"
},
"engines": {
"node": ">=6.9.0"
@@ -1070,85 +1025,130 @@
"@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==",
+ "node_modules/@babel/plugin-transform-block-scoped-functions": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz",
+ "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.10.4"
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
},
"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==",
+ "node_modules/@babel/plugin-transform-block-scoping": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz",
+ "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.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==",
+ "node_modules/@babel/plugin-transform-class-properties": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz",
+ "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.10.4"
+ "@babel/helper-create-class-features-plugin": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
},
"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==",
+ "node_modules/@babel/plugin-transform-class-static-block": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz",
+ "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
+ "@babel/helper-create-class-features-plugin": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@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.23.8",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz",
+ "integrity": "sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@babel/helper-compilation-targets": "^7.23.6",
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-function-name": "^7.23.0",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-replace-supers": "^7.22.20",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.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==",
+ "node_modules/@babel/plugin-transform-computed-properties": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz",
+ "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/template": "^7.22.15"
+ },
+ "engines": {
+ "node": ">=6.9.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==",
+ "node_modules/@babel/plugin-transform-destructuring": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz",
+ "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.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==",
+ "node_modules/@babel/plugin-transform-dotall-regex": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz",
+ "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.14.5"
+ "@babel/helper-create-regexp-features-plugin": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1157,13 +1157,13 @@
"@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==",
+ "node_modules/@babel/plugin-transform-duplicate-keys": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz",
+ "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.14.5"
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1172,13 +1172,14 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-syntax-typescript": {
- "version": "7.20.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz",
- "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==",
+ "node_modules/@babel/plugin-transform-dynamic-import": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz",
+ "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.19.0"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.3"
},
"engines": {
"node": ">=6.9.0"
@@ -1187,13 +1188,14 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-arrow-functions": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz",
- "integrity": "sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA==",
+ "node_modules/@babel/plugin-transform-exponentiation-operator": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz",
+ "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.21.5"
+ "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1202,15 +1204,14 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-async-to-generator": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz",
- "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==",
+ "node_modules/@babel/plugin-transform-export-namespace-from": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz",
+ "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==",
"dev": true,
"dependencies": {
- "@babel/helper-module-imports": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/helper-remap-async-to-generator": "^7.18.9"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-export-namespace-from": "^7.8.3"
},
"engines": {
"node": ">=6.9.0"
@@ -1219,13 +1220,14 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-block-scoped-functions": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz",
- "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==",
+ "node_modules/@babel/plugin-transform-for-of": {
+ "version": "7.23.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz",
+ "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1234,13 +1236,15 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-block-scoping": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz",
- "integrity": "sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==",
+ "node_modules/@babel/plugin-transform-function-name": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz",
+ "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.20.2"
+ "@babel/helper-compilation-targets": "^7.22.15",
+ "@babel/helper-function-name": "^7.23.0",
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1249,21 +1253,14 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-classes": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz",
- "integrity": "sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==",
- "dev": true,
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^7.18.6",
- "@babel/helper-compilation-targets": "^7.20.7",
- "@babel/helper-environment-visitor": "^7.18.9",
- "@babel/helper-function-name": "^7.21.0",
- "@babel/helper-optimise-call-expression": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/helper-replace-supers": "^7.20.7",
- "@babel/helper-split-export-declaration": "^7.18.6",
- "globals": "^11.1.0"
+ "node_modules/@babel/plugin-transform-json-strings": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz",
+ "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-json-strings": "^7.8.3"
},
"engines": {
"node": ">=6.9.0"
@@ -1272,14 +1269,13 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-computed-properties": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.21.5.tgz",
- "integrity": "sha512-TR653Ki3pAwxBxUe8srfF3e4Pe3FTA46uaNHYyQwIoM4oWKSoOZiDNyHJ0oIoDIUPSRQbQG7jzgVBX3FPVne1Q==",
+ "node_modules/@babel/plugin-transform-literals": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz",
+ "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.21.5",
- "@babel/template": "^7.20.7"
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1288,13 +1284,14 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-destructuring": {
- "version": "7.21.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz",
- "integrity": "sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==",
+ "node_modules/@babel/plugin-transform-logical-assignment-operators": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz",
+ "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.20.2"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4"
},
"engines": {
"node": ">=6.9.0"
@@ -1303,14 +1300,13 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-dotall-regex": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz",
- "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==",
+ "node_modules/@babel/plugin-transform-member-expression-literals": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz",
+ "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==",
"dev": true,
"dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1319,13 +1315,14 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-duplicate-keys": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz",
- "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==",
+ "node_modules/@babel/plugin-transform-modules-amd": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz",
+ "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.18.9"
+ "@babel/helper-module-transforms": "^7.23.3",
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1334,14 +1331,15 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-exponentiation-operator": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz",
- "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==",
+ "node_modules/@babel/plugin-transform-modules-commonjs": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz",
+ "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==",
"dev": true,
"dependencies": {
- "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-module-transforms": "^7.23.3",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-simple-access": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1350,13 +1348,16 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-for-of": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz",
- "integrity": "sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ==",
+ "node_modules/@babel/plugin-transform-modules-systemjs": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz",
+ "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.21.5"
+ "@babel/helper-hoist-variables": "^7.22.5",
+ "@babel/helper-module-transforms": "^7.23.3",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.20"
},
"engines": {
"node": ">=6.9.0"
@@ -1365,15 +1366,14 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-function-name": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz",
- "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==",
+ "node_modules/@babel/plugin-transform-modules-umd": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz",
+ "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==",
"dev": true,
"dependencies": {
- "@babel/helper-compilation-targets": "^7.18.9",
- "@babel/helper-function-name": "^7.18.9",
- "@babel/helper-plugin-utils": "^7.18.9"
+ "@babel/helper-module-transforms": "^7.23.3",
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1382,13 +1382,29 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-literals": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz",
- "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==",
+ "node_modules/@babel/plugin-transform-named-capturing-groups-regex": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz",
+ "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-new-target": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz",
+ "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.18.9"
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1397,13 +1413,14 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-member-expression-literals": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz",
- "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==",
+ "node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz",
+ "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3"
},
"engines": {
"node": ">=6.9.0"
@@ -1412,14 +1429,14 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-modules-amd": {
- "version": "7.20.11",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz",
- "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==",
+ "node_modules/@babel/plugin-transform-numeric-separator": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz",
+ "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==",
"dev": true,
"dependencies": {
- "@babel/helper-module-transforms": "^7.20.11",
- "@babel/helper-plugin-utils": "^7.20.2"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4"
},
"engines": {
"node": ">=6.9.0"
@@ -1428,15 +1445,17 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-modules-commonjs": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.5.tgz",
- "integrity": "sha512-OVryBEgKUbtqMoB7eG2rs6UFexJi6Zj6FDXx+esBLPTCxCNxAY9o+8Di7IsUGJ+AVhp5ncK0fxWUBd0/1gPhrQ==",
+ "node_modules/@babel/plugin-transform-object-rest-spread": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz",
+ "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==",
"dev": true,
"dependencies": {
- "@babel/helper-module-transforms": "^7.21.5",
- "@babel/helper-plugin-utils": "^7.21.5",
- "@babel/helper-simple-access": "^7.21.5"
+ "@babel/compat-data": "^7.23.3",
+ "@babel/helper-compilation-targets": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-transform-parameters": "^7.23.3"
},
"engines": {
"node": ">=6.9.0"
@@ -1445,16 +1464,14 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-modules-systemjs": {
- "version": "7.20.11",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz",
- "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==",
+ "node_modules/@babel/plugin-transform-object-super": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz",
+ "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==",
"dev": true,
"dependencies": {
- "@babel/helper-hoist-variables": "^7.18.6",
- "@babel/helper-module-transforms": "^7.20.11",
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/helper-validator-identifier": "^7.19.1"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-replace-supers": "^7.22.20"
},
"engines": {
"node": ">=6.9.0"
@@ -1463,14 +1480,14 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-modules-umd": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz",
- "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==",
+ "node_modules/@babel/plugin-transform-optional-catch-binding": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz",
+ "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==",
"dev": true,
"dependencies": {
- "@babel/helper-module-transforms": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3"
},
"engines": {
"node": ">=6.9.0"
@@ -1479,29 +1496,30 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-named-capturing-groups-regex": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz",
- "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==",
+ "node_modules/@babel/plugin-transform-optional-chaining": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz",
+ "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==",
"dev": true,
"dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.20.5",
- "@babel/helper-plugin-utils": "^7.20.2"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
- "@babel/core": "^7.0.0"
+ "@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-new-target": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz",
- "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==",
+ "node_modules/@babel/plugin-transform-parameters": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz",
+ "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1510,14 +1528,14 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-object-super": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz",
- "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==",
+ "node_modules/@babel/plugin-transform-private-methods": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz",
+ "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.18.6",
- "@babel/helper-replace-supers": "^7.18.6"
+ "@babel/helper-create-class-features-plugin": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1526,13 +1544,16 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-parameters": {
- "version": "7.21.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz",
- "integrity": "sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==",
+ "node_modules/@babel/plugin-transform-private-property-in-object": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz",
+ "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.20.2"
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@babel/helper-create-class-features-plugin": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1542,12 +1563,12 @@
}
},
"node_modules/@babel/plugin-transform-property-literals": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz",
- "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz",
+ "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1557,13 +1578,13 @@
}
},
"node_modules/@babel/plugin-transform-regenerator": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz",
- "integrity": "sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz",
+ "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.21.5",
- "regenerator-transform": "^0.15.1"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "regenerator-transform": "^0.15.2"
},
"engines": {
"node": ">=6.9.0"
@@ -1573,12 +1594,12 @@
}
},
"node_modules/@babel/plugin-transform-reserved-words": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz",
- "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz",
+ "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1619,12 +1640,12 @@
}
},
"node_modules/@babel/plugin-transform-shorthand-properties": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz",
- "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz",
+ "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1634,13 +1655,13 @@
}
},
"node_modules/@babel/plugin-transform-spread": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz",
- "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz",
+ "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1650,12 +1671,12 @@
}
},
"node_modules/@babel/plugin-transform-sticky-regex": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz",
- "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz",
+ "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1665,12 +1686,12 @@
}
},
"node_modules/@babel/plugin-transform-template-literals": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz",
- "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz",
+ "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.18.9"
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1680,12 +1701,12 @@
}
},
"node_modules/@babel/plugin-transform-typeof-symbol": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz",
- "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz",
+ "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.18.9"
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1695,12 +1716,28 @@
}
},
"node_modules/@babel/plugin-transform-unicode-escapes": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.21.5.tgz",
- "integrity": "sha512-LYm/gTOwZqsYohlvFUe/8Tujz75LqqVC2w+2qPHLR+WyWHGCZPN1KBpJCJn+4Bk4gOkQy/IXKIge6az5MqwlOg==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz",
+ "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-unicode-property-regex": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz",
+ "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.21.5"
+ "@babel/helper-create-regexp-features-plugin": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1710,13 +1747,13 @@
}
},
"node_modules/@babel/plugin-transform-unicode-regex": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz",
- "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz",
+ "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==",
"dev": true,
"dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-create-regexp-features-plugin": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1725,39 +1762,43 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/plugin-transform-unicode-sets-regex": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz",
+ "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
"node_modules/@babel/preset-env": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.21.5.tgz",
- "integrity": "sha512-wH00QnTTldTbf/IefEVyChtRdw5RJvODT/Vb4Vcxq1AZvtXj6T0YeX0cAcXhI6/BdGuiP3GcNIL4OQbI2DVNxg==",
- "dev": true,
- "dependencies": {
- "@babel/compat-data": "^7.21.5",
- "@babel/helper-compilation-targets": "^7.21.5",
- "@babel/helper-plugin-utils": "^7.21.5",
- "@babel/helper-validator-option": "^7.21.0",
- "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6",
- "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.20.7",
- "@babel/plugin-proposal-async-generator-functions": "^7.20.7",
- "@babel/plugin-proposal-class-properties": "^7.18.6",
- "@babel/plugin-proposal-class-static-block": "^7.21.0",
- "@babel/plugin-proposal-dynamic-import": "^7.18.6",
- "@babel/plugin-proposal-export-namespace-from": "^7.18.9",
- "@babel/plugin-proposal-json-strings": "^7.18.6",
- "@babel/plugin-proposal-logical-assignment-operators": "^7.20.7",
- "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
- "@babel/plugin-proposal-numeric-separator": "^7.18.6",
- "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
- "@babel/plugin-proposal-optional-catch-binding": "^7.18.6",
- "@babel/plugin-proposal-optional-chaining": "^7.21.0",
- "@babel/plugin-proposal-private-methods": "^7.18.6",
- "@babel/plugin-proposal-private-property-in-object": "^7.21.0",
- "@babel/plugin-proposal-unicode-property-regex": "^7.18.6",
+ "version": "7.23.8",
+ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.8.tgz",
+ "integrity": "sha512-lFlpmkApLkEP6woIKprO6DO60RImpatTQKtz4sUcDjVcK8M8mQ4sZsuxaTMNOZf0sqAq/ReYW1ZBHnOQwKpLWA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/compat-data": "^7.23.5",
+ "@babel/helper-compilation-targets": "^7.23.6",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-validator-option": "^7.23.5",
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3",
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3",
+ "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.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.20.0",
+ "@babel/plugin-syntax-import-assertions": "^7.23.3",
+ "@babel/plugin-syntax-import-attributes": "^7.23.3",
"@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",
@@ -1768,45 +1809,61 @@
"@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-transform-arrow-functions": "^7.21.5",
- "@babel/plugin-transform-async-to-generator": "^7.20.7",
- "@babel/plugin-transform-block-scoped-functions": "^7.18.6",
- "@babel/plugin-transform-block-scoping": "^7.21.0",
- "@babel/plugin-transform-classes": "^7.21.0",
- "@babel/plugin-transform-computed-properties": "^7.21.5",
- "@babel/plugin-transform-destructuring": "^7.21.3",
- "@babel/plugin-transform-dotall-regex": "^7.18.6",
- "@babel/plugin-transform-duplicate-keys": "^7.18.9",
- "@babel/plugin-transform-exponentiation-operator": "^7.18.6",
- "@babel/plugin-transform-for-of": "^7.21.5",
- "@babel/plugin-transform-function-name": "^7.18.9",
- "@babel/plugin-transform-literals": "^7.18.9",
- "@babel/plugin-transform-member-expression-literals": "^7.18.6",
- "@babel/plugin-transform-modules-amd": "^7.20.11",
- "@babel/plugin-transform-modules-commonjs": "^7.21.5",
- "@babel/plugin-transform-modules-systemjs": "^7.20.11",
- "@babel/plugin-transform-modules-umd": "^7.18.6",
- "@babel/plugin-transform-named-capturing-groups-regex": "^7.20.5",
- "@babel/plugin-transform-new-target": "^7.18.6",
- "@babel/plugin-transform-object-super": "^7.18.6",
- "@babel/plugin-transform-parameters": "^7.21.3",
- "@babel/plugin-transform-property-literals": "^7.18.6",
- "@babel/plugin-transform-regenerator": "^7.21.5",
- "@babel/plugin-transform-reserved-words": "^7.18.6",
- "@babel/plugin-transform-shorthand-properties": "^7.18.6",
- "@babel/plugin-transform-spread": "^7.20.7",
- "@babel/plugin-transform-sticky-regex": "^7.18.6",
- "@babel/plugin-transform-template-literals": "^7.18.9",
- "@babel/plugin-transform-typeof-symbol": "^7.18.9",
- "@babel/plugin-transform-unicode-escapes": "^7.21.5",
- "@babel/plugin-transform-unicode-regex": "^7.18.6",
- "@babel/preset-modules": "^0.1.5",
- "@babel/types": "^7.21.5",
- "babel-plugin-polyfill-corejs2": "^0.3.3",
- "babel-plugin-polyfill-corejs3": "^0.6.0",
- "babel-plugin-polyfill-regenerator": "^0.4.1",
- "core-js-compat": "^3.25.1",
- "semver": "^6.3.0"
+ "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
+ "@babel/plugin-transform-arrow-functions": "^7.23.3",
+ "@babel/plugin-transform-async-generator-functions": "^7.23.7",
+ "@babel/plugin-transform-async-to-generator": "^7.23.3",
+ "@babel/plugin-transform-block-scoped-functions": "^7.23.3",
+ "@babel/plugin-transform-block-scoping": "^7.23.4",
+ "@babel/plugin-transform-class-properties": "^7.23.3",
+ "@babel/plugin-transform-class-static-block": "^7.23.4",
+ "@babel/plugin-transform-classes": "^7.23.8",
+ "@babel/plugin-transform-computed-properties": "^7.23.3",
+ "@babel/plugin-transform-destructuring": "^7.23.3",
+ "@babel/plugin-transform-dotall-regex": "^7.23.3",
+ "@babel/plugin-transform-duplicate-keys": "^7.23.3",
+ "@babel/plugin-transform-dynamic-import": "^7.23.4",
+ "@babel/plugin-transform-exponentiation-operator": "^7.23.3",
+ "@babel/plugin-transform-export-namespace-from": "^7.23.4",
+ "@babel/plugin-transform-for-of": "^7.23.6",
+ "@babel/plugin-transform-function-name": "^7.23.3",
+ "@babel/plugin-transform-json-strings": "^7.23.4",
+ "@babel/plugin-transform-literals": "^7.23.3",
+ "@babel/plugin-transform-logical-assignment-operators": "^7.23.4",
+ "@babel/plugin-transform-member-expression-literals": "^7.23.3",
+ "@babel/plugin-transform-modules-amd": "^7.23.3",
+ "@babel/plugin-transform-modules-commonjs": "^7.23.3",
+ "@babel/plugin-transform-modules-systemjs": "^7.23.3",
+ "@babel/plugin-transform-modules-umd": "^7.23.3",
+ "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5",
+ "@babel/plugin-transform-new-target": "^7.23.3",
+ "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4",
+ "@babel/plugin-transform-numeric-separator": "^7.23.4",
+ "@babel/plugin-transform-object-rest-spread": "^7.23.4",
+ "@babel/plugin-transform-object-super": "^7.23.3",
+ "@babel/plugin-transform-optional-catch-binding": "^7.23.4",
+ "@babel/plugin-transform-optional-chaining": "^7.23.4",
+ "@babel/plugin-transform-parameters": "^7.23.3",
+ "@babel/plugin-transform-private-methods": "^7.23.3",
+ "@babel/plugin-transform-private-property-in-object": "^7.23.4",
+ "@babel/plugin-transform-property-literals": "^7.23.3",
+ "@babel/plugin-transform-regenerator": "^7.23.3",
+ "@babel/plugin-transform-reserved-words": "^7.23.3",
+ "@babel/plugin-transform-shorthand-properties": "^7.23.3",
+ "@babel/plugin-transform-spread": "^7.23.3",
+ "@babel/plugin-transform-sticky-regex": "^7.23.3",
+ "@babel/plugin-transform-template-literals": "^7.23.3",
+ "@babel/plugin-transform-typeof-symbol": "^7.23.3",
+ "@babel/plugin-transform-unicode-escapes": "^7.23.3",
+ "@babel/plugin-transform-unicode-property-regex": "^7.23.3",
+ "@babel/plugin-transform-unicode-regex": "^7.23.3",
+ "@babel/plugin-transform-unicode-sets-regex": "^7.23.3",
+ "@babel/preset-modules": "0.1.6-no-external-plugins",
+ "babel-plugin-polyfill-corejs2": "^0.4.7",
+ "babel-plugin-polyfill-corejs3": "^0.8.7",
+ "babel-plugin-polyfill-regenerator": "^0.5.4",
+ "core-js-compat": "^3.31.0",
+ "semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
@@ -1815,32 +1872,60 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/preset-env/node_modules/@babel/helper-define-polyfill-provider": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz",
+ "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==",
+ "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/preset-env/node_modules/babel-plugin-polyfill-corejs2": {
+ "version": "0.4.7",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz",
+ "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/compat-data": "^7.22.6",
+ "@babel/helper-define-polyfill-provider": "^0.4.4",
+ "semver": "^6.3.1"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
"node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-regenerator": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz",
- "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==",
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz",
+ "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==",
"dev": true,
"dependencies": {
- "@babel/helper-define-polyfill-provider": "^0.3.3"
+ "@babel/helper-define-polyfill-provider": "^0.4.4"
},
"peerDependencies": {
- "@babel/core": "^7.0.0-0"
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
"node_modules/@babel/preset-modules": {
- "version": "0.1.5",
- "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz",
- "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==",
+ "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/plugin-proposal-unicode-property-regex": "^7.4.4",
- "@babel/plugin-transform-dotall-regex": "^7.4.4",
"@babel/types": "^7.4.4",
"esutils": "^2.0.2"
},
"peerDependencies": {
- "@babel/core": "^7.0.0-0"
+ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0"
}
},
"node_modules/@babel/regjsgen": {
@@ -1861,32 +1946,32 @@
}
},
"node_modules/@babel/template": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz",
- "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
+ "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
"dependencies": {
- "@babel/code-frame": "^7.18.6",
- "@babel/parser": "^7.20.7",
- "@babel/types": "^7.20.7"
+ "@babel/code-frame": "^7.22.13",
+ "@babel/parser": "^7.22.15",
+ "@babel/types": "^7.22.15"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz",
- "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==",
- "dependencies": {
- "@babel/code-frame": "^7.21.4",
- "@babel/generator": "^7.21.5",
- "@babel/helper-environment-visitor": "^7.21.5",
- "@babel/helper-function-name": "^7.21.0",
- "@babel/helper-hoist-variables": "^7.18.6",
- "@babel/helper-split-export-declaration": "^7.18.6",
- "@babel/parser": "^7.21.5",
- "@babel/types": "^7.21.5",
- "debug": "^4.1.0",
+ "version": "7.23.7",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz",
+ "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==",
+ "dependencies": {
+ "@babel/code-frame": "^7.23.5",
+ "@babel/generator": "^7.23.6",
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-function-name": "^7.23.0",
+ "@babel/helper-hoist-variables": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "@babel/parser": "^7.23.6",
+ "@babel/types": "^7.23.6",
+ "debug": "^4.3.1",
"globals": "^11.1.0"
},
"engines": {
@@ -1894,12 +1979,12 @@
}
},
"node_modules/@babel/types": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz",
- "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==",
+ "version": "7.23.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz",
+ "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==",
"dependencies": {
- "@babel/helper-string-parser": "^7.21.5",
- "@babel/helper-validator-identifier": "^7.19.1",
+ "@babel/helper-string-parser": "^7.23.4",
+ "@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
},
"engines": {
@@ -1912,32 +1997,6 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true
},
- "node_modules/@cspotcode/source-map-support": {
- "version": "0.8.1",
- "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
- "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
- "dev": true,
- "optional": true,
- "peer": true,
- "dependencies": {
- "@jridgewell/trace-mapping": "0.3.9"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
- "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
- "dev": true,
- "optional": true,
- "peer": true,
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.0.3",
- "@jridgewell/sourcemap-codec": "^1.4.10"
- }
- },
"node_modules/@discoveryjs/json-ext": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz",
@@ -1986,23 +2045,23 @@
}
},
"node_modules/@eslint-community/regexpp": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.0.tgz",
- "integrity": "sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==",
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz",
+ "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==",
"dev": true,
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
"node_modules/@eslint/eslintrc": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz",
- "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==",
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
- "espree": "^9.5.2",
+ "espree": "^9.6.0",
"globals": "^13.19.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
@@ -2024,9 +2083,9 @@
"dev": true
},
"node_modules/@eslint/eslintrc/node_modules/globals": {
- "version": "13.20.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
- "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
+ "version": "13.23.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz",
+ "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==",
"dev": true,
"dependencies": {
"type-fest": "^0.20.2"
@@ -2063,30 +2122,65 @@
}
},
"node_modules/@eslint/js": {
- "version": "8.41.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.41.0.tgz",
- "integrity": "sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz",
+ "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@ethereumjs/common": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.5.0.tgz",
- "integrity": "sha512-DEHjW6e38o+JmB/NO3GZBpW4lpaiBpkFgXF6jLcJ6gETBYpEyaA5nTimsWBUJR3Vmtm/didUEbNjajskugZORg==",
+ "version": "2.6.5",
+ "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.6.5.tgz",
+ "integrity": "sha512-lRyVQOeCDaIVtgfbowla32pzeDv2Obr8oR8Put5RdUBNRGr1VGPGQNGP6elWIpgK3YdpzqTOh4GyUGOureVeeA==",
"dependencies": {
"crc-32": "^1.2.0",
- "ethereumjs-util": "^7.1.1"
+ "ethereumjs-util": "^7.1.5"
+ }
+ },
+ "node_modules/@ethereumjs/rlp": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-4.0.1.tgz",
+ "integrity": "sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==",
+ "bin": {
+ "rlp": "bin/rlp"
+ },
+ "engines": {
+ "node": ">=14"
}
},
"node_modules/@ethereumjs/tx": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.3.2.tgz",
- "integrity": "sha512-6AaJhwg4ucmwTvw/1qLaZUX5miWrwZ4nLOUsKyb/HtzS3BMw/CasKhdi1ims9mBKeK9sOJCH4qGKOBGyJCeeog==",
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.5.2.tgz",
+ "integrity": "sha512-gQDNJWKrSDGu2w7w0PzVXVBNMzb7wwdDOmOqczmhNjqFxFuIbhVJDwiGEnxFNC2/b8ifcZzY7MLcluizohRzNw==",
+ "dependencies": {
+ "@ethereumjs/common": "^2.6.4",
+ "ethereumjs-util": "^7.1.5"
+ }
+ },
+ "node_modules/@ethereumjs/util": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-8.1.0.tgz",
+ "integrity": "sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==",
+ "dependencies": {
+ "@ethereumjs/rlp": "^4.0.1",
+ "ethereum-cryptography": "^2.0.0",
+ "micro-ftch": "^0.3.1"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@ethereumjs/util/node_modules/ethereum-cryptography": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz",
+ "integrity": "sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug==",
"dependencies": {
- "@ethereumjs/common": "^2.5.0",
- "ethereumjs-util": "^7.1.2"
+ "@noble/curves": "1.1.0",
+ "@noble/hashes": "1.3.1",
+ "@scure/bip32": "1.3.1",
+ "@scure/bip39": "1.2.1"
}
},
"node_modules/@ethersproject/abi": {
@@ -2464,21 +2558,21 @@
}
},
"node_modules/@fortawesome/fontawesome-free": {
- "version": "6.4.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.0.tgz",
- "integrity": "sha512-0NyytTlPJwB/BF5LtRV8rrABDbe3TdTXqNB3PdZ+UUUZAEIrdOJdmABqKjt4AXwIoJNaRVVZEXxpNrqvE1GAYQ==",
+ "version": "6.5.1",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.1.tgz",
+ "integrity": "sha512-CNy5vSwN3fsUStPRLX7fUYojyuzoEMSXPl7zSLJ8TgtRfjv24LOnOWKT2zYwaHZCJGkdyRnTmstR0P+Ah503Gw==",
"hasInstallScript": true,
"engines": {
"node": ">=6"
}
},
"node_modules/@humanwhocodes/config-array": {
- "version": "0.11.8",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
- "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
+ "version": "0.11.13",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
+ "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
"dev": true,
"dependencies": {
- "@humanwhocodes/object-schema": "^1.2.1",
+ "@humanwhocodes/object-schema": "^2.0.1",
"debug": "^4.1.1",
"minimatch": "^3.0.5"
},
@@ -2500,9 +2594,9 @@
}
},
"node_modules/@humanwhocodes/object-schema": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
- "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
+ "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
"dev": true
},
"node_modules/@istanbuljs/load-nyc-config": {
@@ -2540,16 +2634,16 @@
}
},
"node_modules/@jest/console": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz",
- "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==",
+ "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.5.0",
+ "@jest/types": "^29.6.3",
"@types/node": "*",
"chalk": "^4.0.0",
- "jest-message-util": "^29.5.0",
- "jest-util": "^29.5.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
"slash": "^3.0.0"
},
"engines": {
@@ -2627,37 +2721,37 @@
}
},
"node_modules/@jest/core": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz",
- "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==",
+ "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.5.0",
- "@jest/reporters": "^29.5.0",
- "@jest/test-result": "^29.5.0",
- "@jest/transform": "^29.5.0",
- "@jest/types": "^29.5.0",
+ "@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.5.0",
- "jest-config": "^29.5.0",
- "jest-haste-map": "^29.5.0",
- "jest-message-util": "^29.5.0",
- "jest-regex-util": "^29.4.3",
- "jest-resolve": "^29.5.0",
- "jest-resolve-dependencies": "^29.5.0",
- "jest-runner": "^29.5.0",
- "jest-runtime": "^29.5.0",
- "jest-snapshot": "^29.5.0",
- "jest-util": "^29.5.0",
- "jest-validate": "^29.5.0",
- "jest-watcher": "^29.5.0",
+ "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.5.0",
+ "pretty-format": "^29.7.0",
"slash": "^3.0.0",
"strip-ansi": "^6.0.0"
},
@@ -2744,89 +2838,89 @@
}
},
"node_modules/@jest/environment": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz",
- "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==",
+ "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.5.0",
- "@jest/types": "^29.5.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
"@types/node": "*",
- "jest-mock": "^29.5.0"
+ "jest-mock": "^29.7.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jest/expect": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz",
- "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==",
+ "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.5.0",
- "jest-snapshot": "^29.5.0"
+ "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.5.0",
- "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz",
- "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==",
+ "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.4.3"
+ "jest-get-type": "^29.6.3"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jest/fake-timers": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz",
- "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==",
+ "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.5.0",
+ "@jest/types": "^29.6.3",
"@sinonjs/fake-timers": "^10.0.2",
"@types/node": "*",
- "jest-message-util": "^29.5.0",
- "jest-mock": "^29.5.0",
- "jest-util": "^29.5.0"
+ "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.5.0",
- "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz",
- "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==",
+ "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.5.0",
- "@jest/expect": "^29.5.0",
- "@jest/types": "^29.5.0",
- "jest-mock": "^29.5.0"
+ "@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.5.0",
- "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz",
- "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==",
+ "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.5.0",
- "@jest/test-result": "^29.5.0",
- "@jest/transform": "^29.5.0",
- "@jest/types": "^29.5.0",
- "@jridgewell/trace-mapping": "^0.3.15",
+ "@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",
@@ -2834,13 +2928,13 @@
"glob": "^7.1.3",
"graceful-fs": "^4.2.9",
"istanbul-lib-coverage": "^3.0.0",
- "istanbul-lib-instrument": "^5.1.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.5.0",
- "jest-util": "^29.5.0",
- "jest-worker": "^29.5.0",
+ "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",
@@ -2917,13 +3011,13 @@
}
},
"node_modules/@jest/reporters/node_modules/jest-worker": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz",
- "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==",
+ "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.5.0",
+ "jest-util": "^29.7.0",
"merge-stream": "^2.0.0",
"supports-color": "^8.0.0"
},
@@ -2959,24 +3053,24 @@
}
},
"node_modules/@jest/schemas": {
- "version": "29.4.3",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz",
- "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==",
+ "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.25.16"
+ "@sinclair/typebox": "^0.27.8"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jest/source-map": {
- "version": "29.4.3",
- "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz",
- "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==",
+ "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.15",
+ "@jridgewell/trace-mapping": "^0.3.18",
"callsites": "^3.0.0",
"graceful-fs": "^4.2.9"
},
@@ -2985,13 +3079,13 @@
}
},
"node_modules/@jest/test-result": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz",
- "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==",
+ "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.5.0",
- "@jest/types": "^29.5.0",
+ "@jest/console": "^29.7.0",
+ "@jest/types": "^29.6.3",
"@types/istanbul-lib-coverage": "^2.0.0",
"collect-v8-coverage": "^1.0.0"
},
@@ -3000,14 +3094,14 @@
}
},
"node_modules/@jest/test-sequencer": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz",
- "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==",
+ "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.5.0",
+ "@jest/test-result": "^29.7.0",
"graceful-fs": "^4.2.9",
- "jest-haste-map": "^29.5.0",
+ "jest-haste-map": "^29.7.0",
"slash": "^3.0.0"
},
"engines": {
@@ -3015,22 +3109,22 @@
}
},
"node_modules/@jest/transform": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz",
- "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==",
+ "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.5.0",
- "@jridgewell/trace-mapping": "^0.3.15",
+ "@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.5.0",
- "jest-regex-util": "^29.4.3",
- "jest-util": "^29.5.0",
+ "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",
@@ -3117,12 +3211,12 @@
}
},
"node_modules/@jest/types": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz",
- "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==",
+ "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.4.3",
+ "@jest/schemas": "^29.6.3",
"@types/istanbul-lib-coverage": "^2.0.0",
"@types/istanbul-reports": "^3.0.0",
"@types/node": "*",
@@ -3248,9 +3342,9 @@
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
},
"node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.17",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
- "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
+ "version": "0.3.18",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
+ "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
"dependencies": {
"@jridgewell/resolve-uri": "3.1.0",
"@jridgewell/sourcemap-codec": "1.4.14"
@@ -3266,6 +3360,28 @@
"resolved": "https://registry.npmjs.org/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz",
"integrity": "sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q=="
},
+ "node_modules/@noble/curves": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz",
+ "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==",
+ "dependencies": {
+ "@noble/hashes": "1.3.1"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@noble/hashes": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
+ "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -3301,10 +3417,43 @@
"node": ">= 8"
}
},
+ "node_modules/@scure/base": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.3.tgz",
+ "integrity": "sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==",
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@scure/bip32": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz",
+ "integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==",
+ "dependencies": {
+ "@noble/curves": "~1.1.0",
+ "@noble/hashes": "~1.3.1",
+ "@scure/base": "~1.1.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@scure/bip39": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz",
+ "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==",
+ "dependencies": {
+ "@noble/hashes": "~1.3.0",
+ "@scure/base": "~1.1.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/@sinclair/typebox": {
- "version": "0.25.21",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.21.tgz",
- "integrity": "sha512-gFukHN4t8K4+wVC+ECqeqwzBDeFeTzBXroBTqE6vcWrQGbEUpHO7LYdG0f4xnvYq4VOEwITSlHlp0JBAIFMS/g==",
+ "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/@sindresorhus/is": {
@@ -3318,6 +3467,18 @@
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
+ "node_modules/@sindresorhus/merge-streams": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz",
+ "integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/@sinonjs/commons": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz",
@@ -3375,42 +3536,10 @@
"node": ">=10.13.0"
}
},
- "node_modules/@tsconfig/node10": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
- "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
- "dev": true,
- "optional": true,
- "peer": true
- },
- "node_modules/@tsconfig/node12": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
- "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
- "dev": true,
- "optional": true,
- "peer": true
- },
- "node_modules/@tsconfig/node14": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
- "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
- "dev": true,
- "optional": true,
- "peer": true
- },
- "node_modules/@tsconfig/node16": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
- "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
- "dev": true,
- "optional": true,
- "peer": true
- },
"node_modules/@types/babel__core": {
- "version": "7.20.0",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz",
- "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==",
+ "version": "7.20.1",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz",
+ "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.20.7",
@@ -3440,12 +3569,12 @@
}
},
"node_modules/@types/babel__traverse": {
- "version": "7.18.3",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz",
- "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==",
+ "version": "7.20.1",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz",
+ "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==",
"dev": true,
"dependencies": {
- "@babel/types": "^7.3.0"
+ "@babel/types": "^7.20.7"
}
},
"node_modules/@types/bn.js": {
@@ -3503,9 +3632,9 @@
}
},
"node_modules/@types/http-cache-semantics": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
- "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ=="
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz",
+ "integrity": "sha512-V46MYLFp08Wf2mmaBhvgjStM3tPa+2GAdy/iqoX+noX1//zje2x4XmrIU0cAwyClATsTmahbtoQ2EwP7I5WSiA=="
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.4",
@@ -3575,16 +3704,10 @@
"@types/node": "*"
}
},
- "node_modules/@types/prettier": {
- "version": "2.7.2",
- "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz",
- "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==",
- "dev": true
- },
"node_modules/@types/responselike": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
- "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.2.tgz",
+ "integrity": "sha512-/4YQT5Kp6HxUDb4yhRkm0bJ7TbjvTddqX7PZ5hz6qV3pxSo72f/6YPRo+Mu2DU307tm9IioO69l7uAwn5XNcFA==",
"dependencies": {
"@types/node": "*"
}
@@ -3624,6 +3747,12 @@
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==",
"dev": true
},
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
+ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
+ "dev": true
+ },
"node_modules/@walletconnect/browser-utils": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@walletconnect/browser-utils/-/browser-utils-1.8.0.tgz",
@@ -4016,9 +4145,9 @@
}
},
"node_modules/@webpack-cli/configtest": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.0.tgz",
- "integrity": "sha512-K/vuv72vpfSEZoo5KIU0a2FsEoYdW0DUMtMpB5X3LlUwshetMZRZRxB7sCsVji/lFaSxtQQ3aM9O4eMolXkU9w==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz",
+ "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==",
"dev": true,
"engines": {
"node": ">=14.15.0"
@@ -4029,9 +4158,9 @@
}
},
"node_modules/@webpack-cli/info": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz",
- "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz",
+ "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==",
"dev": true,
"engines": {
"node": ">=14.15.0"
@@ -4042,9 +4171,9 @@
}
},
"node_modules/@webpack-cli/serve": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.4.tgz",
- "integrity": "sha512-0xRgjgDLdz6G7+vvDLlaRpFatJaJ69uTalZLRSMX5B3VUrDmXcrVA3+6fXXQgmYz7bY9AAgs348XQdmtLsK41A==",
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz",
+ "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==",
"dev": true,
"engines": {
"node": ">=14.15.0"
@@ -4103,9 +4232,9 @@
}
},
"node_modules/acorn": {
- "version": "8.8.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
- "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==",
+ "version": "8.9.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
+ "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
@@ -4136,6 +4265,15 @@
"node": ">=0.4.0"
}
},
+ "node_modules/acorn-import-assertions": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz",
+ "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
+ "dev": true,
+ "peerDependencies": {
+ "acorn": "^8"
+ }
+ },
"node_modules/acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
@@ -4282,14 +4420,6 @@
"node": ">= 8"
}
},
- "node_modules/arg": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
- "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
- "dev": true,
- "optional": true,
- "peer": true
- },
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -4299,21 +4429,34 @@
"sprintf-js": "~1.0.2"
}
},
+ "node_modules/array-buffer-byte-length": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
+ "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "is-array-buffer": "^3.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
},
"node_modules/array-includes": {
- "version": "3.1.6",
- "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz",
- "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==",
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz",
+ "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
- "get-intrinsic": "^1.1.3",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "get-intrinsic": "^1.2.1",
"is-string": "^1.0.7"
},
"engines": {
@@ -4323,15 +4466,34 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/array.prototype.findlastindex": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz",
+ "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "es-shim-unscopables": "^1.0.0",
+ "get-intrinsic": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/array.prototype.flat": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz",
- "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz",
+ "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
"es-shim-unscopables": "^1.0.0"
},
"engines": {
@@ -4342,15 +4504,36 @@
}
},
"node_modules/array.prototype.flatmap": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz",
- "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz",
+ "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "es-shim-unscopables": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/arraybuffer.prototype.slice": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz",
+ "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==",
"dev": true,
"dependencies": {
+ "array-buffer-byte-length": "^1.0.0",
"call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
- "es-shim-unscopables": "^1.0.0"
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "get-intrinsic": "^1.2.1",
+ "is-array-buffer": "^3.0.2",
+ "is-shared-array-buffer": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
@@ -4384,14 +4567,15 @@
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
},
"node_modules/assert": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz",
- "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz",
+ "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==",
"dependencies": {
- "es6-object-assign": "^1.1.0",
- "is-nan": "^1.2.1",
- "object-is": "^1.0.1",
- "util": "^0.12.0"
+ "call-bind": "^1.0.2",
+ "is-nan": "^1.3.2",
+ "object-is": "^1.1.5",
+ "object.assign": "^4.1.4",
+ "util": "^0.12.5"
}
},
"node_modules/assert-plus": {
@@ -4450,9 +4634,9 @@
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"node_modules/autoprefixer": {
- "version": "10.4.14",
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz",
- "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==",
+ "version": "10.4.16",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz",
+ "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==",
"dev": true,
"funding": [
{
@@ -4462,12 +4646,16 @@
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
- "browserslist": "^4.21.5",
- "caniuse-lite": "^1.0.30001464",
- "fraction.js": "^4.2.0",
+ "browserslist": "^4.21.10",
+ "caniuse-lite": "^1.0.30001538",
+ "fraction.js": "^4.3.6",
"normalize-range": "^0.1.2",
"picocolors": "^1.0.0",
"postcss-value-parser": "^4.2.0"
@@ -4507,15 +4695,15 @@
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
},
"node_modules/babel-jest": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz",
- "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==",
+ "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.5.0",
+ "@jest/transform": "^29.7.0",
"@types/babel__core": "^7.1.14",
"babel-plugin-istanbul": "^6.1.1",
- "babel-preset-jest": "^29.5.0",
+ "babel-preset-jest": "^29.6.3",
"chalk": "^4.0.0",
"graceful-fs": "^4.2.9",
"slash": "^3.0.0"
@@ -4598,12 +4786,12 @@
}
},
"node_modules/babel-loader": {
- "version": "9.1.2",
- "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz",
- "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==",
+ "version": "9.1.3",
+ "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz",
+ "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==",
"dev": true,
"dependencies": {
- "find-cache-dir": "^3.3.2",
+ "find-cache-dir": "^4.0.0",
"schema-utils": "^4.0.0"
},
"engines": {
@@ -4630,10 +4818,26 @@
"node": ">=8"
}
},
+ "node_modules/babel-plugin-istanbul/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/babel-plugin-jest-hoist": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz",
- "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==",
+ "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",
@@ -4659,16 +4863,32 @@
}
},
"node_modules/babel-plugin-polyfill-corejs3": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz",
- "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==",
+ "version": "0.8.7",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz",
+ "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==",
"dev": true,
"dependencies": {
- "@babel/helper-define-polyfill-provider": "^0.3.3",
- "core-js-compat": "^3.25.1"
+ "@babel/helper-define-polyfill-provider": "^0.4.4",
+ "core-js-compat": "^3.33.1"
},
"peerDependencies": {
- "@babel/core": "^7.0.0-0"
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz",
+ "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==",
+ "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-plugin-polyfill-regenerator": {
@@ -4725,12 +4945,12 @@
}
},
"node_modules/babel-preset-jest": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz",
- "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==",
+ "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.5.0",
+ "babel-plugin-jest-hoist": "^29.6.3",
"babel-preset-current-node-syntax": "^1.0.0"
},
"engines": {
@@ -4802,9 +5022,9 @@
}
},
"node_modules/bignumber.js": {
- "version": "9.1.1",
- "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz",
- "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==",
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
+ "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==",
"engines": {
"node": "*"
}
@@ -4979,19 +5199,22 @@
}
},
"node_modules/browserify-sign": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz",
- "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz",
+ "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==",
"dependencies": {
- "bn.js": "^5.1.1",
- "browserify-rsa": "^4.0.1",
+ "bn.js": "^5.2.1",
+ "browserify-rsa": "^4.1.0",
"create-hash": "^1.2.0",
"create-hmac": "^1.1.7",
- "elliptic": "^6.5.3",
+ "elliptic": "^6.5.4",
"inherits": "^2.0.4",
- "parse-asn1": "^5.1.5",
- "readable-stream": "^3.6.0",
- "safe-buffer": "^5.2.0"
+ "parse-asn1": "^5.1.6",
+ "readable-stream": "^3.6.2",
+ "safe-buffer": "^5.2.1"
+ },
+ "engines": {
+ "node": ">= 4"
}
},
"node_modules/browserify-sign/node_modules/safe-buffer": {
@@ -5014,9 +5237,9 @@
]
},
"node_modules/browserslist": {
- "version": "4.21.5",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
- "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==",
+ "version": "4.22.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz",
+ "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==",
"funding": [
{
"type": "opencollective",
@@ -5025,13 +5248,17 @@
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
- "caniuse-lite": "^1.0.30001449",
- "electron-to-chromium": "^1.4.284",
- "node-releases": "^2.0.8",
- "update-browserslist-db": "^1.0.10"
+ "caniuse-lite": "^1.0.30001565",
+ "electron-to-chromium": "^1.4.601",
+ "node-releases": "^2.0.14",
+ "update-browserslist-db": "^1.0.13"
},
"bin": {
"browserslist": "cli.js"
@@ -5195,9 +5422,9 @@
}
},
"node_modules/cacheable-request": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz",
- "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==",
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz",
+ "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
"dependencies": {
"clone-response": "^1.0.2",
"get-stream": "^5.1.0",
@@ -5234,12 +5461,13 @@
}
},
"node_modules/call-bind": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
- "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
+ "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==",
"dependencies": {
- "function-bind": "^1.1.1",
- "get-intrinsic": "^1.0.2"
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.1",
+ "set-function-length": "^1.1.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -5280,9 +5508,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001464",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001464.tgz",
- "integrity": "sha512-oww27MtUmusatpRpCGSOneQk2/l5czXANDSFvsc7VuOQ86s3ANhZetpwXNf1zY/zdfP63Xvjz325DAdAoES13g==",
+ "version": "1.0.30001568",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001568.tgz",
+ "integrity": "sha512-vSUkH84HontZJ88MiNrOau1EBrCqEQYgkC5gIySiDlpsm8sGVrhU7Kx4V6h0tnqaHzIHZv08HlJIwPbL4XL9+A==",
"funding": [
{
"type": "opencollective",
@@ -5291,6 +5519,10 @@
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
}
]
},
@@ -5338,9 +5570,9 @@
}
},
"node_modules/chart.js": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.0.tgz",
- "integrity": "sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==",
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz",
+ "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
@@ -5469,9 +5701,9 @@
}
},
"node_modules/cjs-module-lexer": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz",
- "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==",
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz",
+ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==",
"dev": true
},
"node_modules/class-is": {
@@ -5547,9 +5779,9 @@
}
},
"node_modules/collect-v8-coverage": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz",
- "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==",
+ "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": {
@@ -5597,10 +5829,10 @@
"node": ">= 10"
}
},
- "node_modules/commondir": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
- "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+ "node_modules/common-path-prefix": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz",
+ "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==",
"dev": true
},
"node_modules/concat-map": {
@@ -5661,6 +5893,7 @@
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
"integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==",
+ "dev": true,
"dependencies": {
"safe-buffer": "~5.1.1"
}
@@ -5692,20 +5925,20 @@
}
},
"node_modules/copy-webpack-plugin": {
- "version": "11.0.0",
- "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz",
- "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==",
+ "version": "12.0.1",
+ "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.1.tgz",
+ "integrity": "sha512-dhMfjJMYKDmmbG6Yn2pRSs1g8FgeQRtbE/JM6VAM9Xouk3KO1UVrwlLHLXxaI5F+o9WgnRfhFZzY9eV34O2gZQ==",
"dev": true,
"dependencies": {
- "fast-glob": "^3.2.11",
+ "fast-glob": "^3.3.2",
"glob-parent": "^6.0.1",
- "globby": "^13.1.1",
+ "globby": "^14.0.0",
"normalize-path": "^3.0.0",
- "schema-utils": "^4.0.0",
- "serialize-javascript": "^6.0.0"
+ "schema-utils": "^4.2.0",
+ "serialize-javascript": "^6.0.2"
},
"engines": {
- "node": ">= 14.15.0"
+ "node": ">= 18.12.0"
},
"funding": {
"type": "opencollective",
@@ -5716,9 +5949,9 @@
}
},
"node_modules/core-js": {
- "version": "3.30.2",
- "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.2.tgz",
- "integrity": "sha512-uBJiDmwqsbJCWHAwjrx3cvjbMXP7xD72Dmsn5LOJpiRmE3WbBbN5rCqQ2Qh6Ek6/eOrjlWngEynBWo4VxerQhg==",
+ "version": "3.35.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.0.tgz",
+ "integrity": "sha512-ntakECeqg81KqMueeGJ79Q5ZgQNR+6eaE8sxGCx62zMbAIj65q+uYvatToew3m6eAGdU4gNZwpZ34NMe4GYswg==",
"hasInstallScript": true,
"funding": {
"type": "opencollective",
@@ -5726,11 +5959,11 @@
}
},
"node_modules/core-js-compat": {
- "version": "3.25.1",
- "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.25.1.tgz",
- "integrity": "sha512-pOHS7O0i8Qt4zlPW/eIFjwp+NrTPx+wTL0ctgI2fHn31sZOq89rDsmtc/A2vAX7r6shl+bmVI+678He46jgBlw==",
+ "version": "3.35.0",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.0.tgz",
+ "integrity": "sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==",
"dependencies": {
- "browserslist": "^4.21.3"
+ "browserslist": "^4.22.2"
},
"funding": {
"type": "opencollective",
@@ -5755,14 +5988,14 @@
}
},
"node_modules/cosmiconfig": {
- "version": "8.1.3",
- "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz",
- "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==",
+ "version": "8.3.6",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
+ "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
"dev": true,
"dependencies": {
- "import-fresh": "^3.2.1",
+ "import-fresh": "^3.3.0",
"js-yaml": "^4.1.0",
- "parse-json": "^5.0.0",
+ "parse-json": "^5.2.0",
"path-type": "^4.0.0"
},
"engines": {
@@ -5770,6 +6003,14 @@
},
"funding": {
"url": "https://github.com/sponsors/d-fischer"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.9.5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
}
},
"node_modules/cosmiconfig/node_modules/argparse": {
@@ -5840,13 +6081,96 @@
"sha.js": "^2.4.8"
}
},
- "node_modules/create-require": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
- "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "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,
- "optional": true,
- "peer": 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-fetch": {
"version": "2.2.6",
@@ -5913,55 +6237,35 @@
}
},
"node_modules/css-loader": {
- "version": "5.2.7",
- "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz",
- "integrity": "sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==",
+ "version": "6.9.0",
+ "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.9.0.tgz",
+ "integrity": "sha512-3I5Nu4ytWlHvOP6zItjiHlefBNtrH+oehq8tnQa2kO305qpVyx9XNIT1CXIj5bgCJs7qICBCkgCYxQLKPANoLA==",
"dev": true,
"dependencies": {
"icss-utils": "^5.1.0",
- "loader-utils": "^2.0.0",
- "postcss": "^8.2.15",
+ "postcss": "^8.4.31",
"postcss-modules-extract-imports": "^3.0.0",
- "postcss-modules-local-by-default": "^4.0.0",
- "postcss-modules-scope": "^3.0.0",
+ "postcss-modules-local-by-default": "^4.0.3",
+ "postcss-modules-scope": "^3.1.0",
"postcss-modules-values": "^4.0.0",
- "postcss-value-parser": "^4.1.0",
- "schema-utils": "^3.0.0",
- "semver": "^7.3.5"
+ "postcss-value-parser": "^4.2.0",
+ "semver": "^7.5.4"
},
"engines": {
- "node": ">= 10.13.0"
+ "node": ">= 12.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
- "webpack": "^4.27.0 || ^5.0.0"
- }
- },
- "node_modules/css-loader/node_modules/schema-utils": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
- "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
- "dev": true,
- "dependencies": {
- "@types/json-schema": "^7.0.8",
- "ajv": "^6.12.5",
- "ajv-keywords": "^3.5.2"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
+ "webpack": "^5.0.0"
}
},
"node_modules/css-loader/node_modules/semver": {
- "version": "7.3.5",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
- "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
@@ -5974,17 +6278,17 @@
}
},
"node_modules/css-minimizer-webpack-plugin": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.0.tgz",
- "integrity": "sha512-1wZ/PYvg+ZKwi5FX6YrvbB31jMAdurS+CmRQLwWCVSlfzJC85l/a6RVICqUHFa+jXyhilfnCyjafzJGbmz5tcA==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz",
+ "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==",
"dev": true,
"dependencies": {
- "cssnano": "^6.0.0",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "cssnano": "^6.0.1",
"jest-worker": "^29.4.3",
- "postcss": "^8.4.21",
- "schema-utils": "^4.0.0",
- "serialize-javascript": "^6.0.1",
- "source-map": "^0.6.1"
+ "postcss": "^8.4.24",
+ "schema-utils": "^4.0.1",
+ "serialize-javascript": "^6.0.1"
},
"engines": {
"node": ">= 14.15.0"
@@ -6125,12 +6429,12 @@
"integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4="
},
"node_modules/cssnano": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.0.tgz",
- "integrity": "sha512-RGlcbzGhzEBCHuQe3k+Udyj5M00z0pm9S+VurHXFEOXxH+y0sVrJH2sMzoyz2d8N1EScazg+DVvmgyx0lurwwA==",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.1.tgz",
+ "integrity": "sha512-fVO1JdJ0LSdIGJq68eIxOqFpIJrZqXUsBt8fkrBcztCQqAjQD51OhZp7tc0ImcbwXD4k7ny84QTV90nZhmqbkg==",
"dev": true,
"dependencies": {
- "cssnano-preset-default": "^6.0.0",
+ "cssnano-preset-default": "^6.0.1",
"lilconfig": "^2.1.0"
},
"engines": {
@@ -6145,14 +6449,14 @@
}
},
"node_modules/cssnano-preset-default": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.0.tgz",
- "integrity": "sha512-BDxlaFzObRDXUiCCBQUNQcI+f1/aX2mgoNtXGjV6PG64POcHoDUoX+LgMWw+Q4609QhxwkcSnS65YFs42RA6qQ==",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.1.tgz",
+ "integrity": "sha512-7VzyFZ5zEB1+l1nToKyrRkuaJIx0zi/1npjvZfbBwbtNTzhLtlvYraK/7/uqmX2Wb2aQtd983uuGw79jAjLSuQ==",
"dev": true,
"dependencies": {
"css-declaration-sorter": "^6.3.1",
"cssnano-utils": "^4.0.0",
- "postcss-calc": "^8.2.3",
+ "postcss-calc": "^9.0.0",
"postcss-colormin": "^6.0.0",
"postcss-convert-values": "^6.0.0",
"postcss-discard-comments": "^6.0.0",
@@ -6160,7 +6464,7 @@
"postcss-discard-empty": "^6.0.0",
"postcss-discard-overridden": "^6.0.0",
"postcss-merge-longhand": "^6.0.0",
- "postcss-merge-rules": "^6.0.0",
+ "postcss-merge-rules": "^6.0.1",
"postcss-minify-font-values": "^6.0.0",
"postcss-minify-gradients": "^6.0.0",
"postcss-minify-params": "^6.0.0",
@@ -6354,10 +6658,18 @@
}
},
"node_modules/dedent": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
- "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==",
- "dev": true
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz",
+ "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==",
+ "dev": true,
+ "peerDependencies": {
+ "babel-plugin-macros": "^3.1.0"
+ },
+ "peerDependenciesMeta": {
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
},
"node_modules/deep-eql": {
"version": "3.0.1",
@@ -6377,9 +6689,9 @@
"dev": true
},
"node_modules/deepmerge": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz",
- "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==",
+ "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"
@@ -6401,10 +6713,23 @@
"abstract-leveldown": "~2.6.0"
}
},
+ "node_modules/define-data-property": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
+ "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==",
+ "dependencies": {
+ "get-intrinsic": "^1.2.1",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/define-properties": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
- "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
+ "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
"dependencies": {
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
@@ -6469,21 +6794,10 @@
"node": ">=8"
}
},
- "node_modules/diff": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
- "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
- "dev": true,
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">=0.3.1"
- }
- },
"node_modules/diff-sequences": {
- "version": "29.4.3",
- "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz",
- "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==",
+ "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"
@@ -6509,18 +6823,6 @@
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.2.tgz",
"integrity": "sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg=="
},
- "node_modules/dir-glob": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
- "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
- "dev": true,
- "dependencies": {
- "path-type": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -6592,14 +6894,14 @@
}
},
"node_modules/domutils": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
- "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
+ "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
"dev": true,
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
- "domhandler": "^5.0.1"
+ "domhandler": "^5.0.3"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
@@ -6625,9 +6927,9 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"node_modules/electron-to-chromium": {
- "version": "1.4.327",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.327.tgz",
- "integrity": "sha512-DIk2H4g/3ZhjgiABJjVdQvUdMlSABOsjeCm6gmUzIdKxAuFrGiJ8QXMm3i09grZdDBMC/d8MELMrdwYRC0+YHg=="
+ "version": "1.4.609",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.609.tgz",
+ "integrity": "sha512-ihiCP7PJmjoGNuLpl7TjNA8pCQWu09vGyjlPYw1Rqww4gvNuCcmvl+44G+2QyJ6S2K4o+wbTS++Xz0YN8Q9ERw=="
},
"node_modules/elliptic": {
"version": "6.5.4",
@@ -6715,9 +7017,9 @@
}
},
"node_modules/enhanced-resolve": {
- "version": "5.14.0",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.0.tgz",
- "integrity": "sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw==",
+ "version": "5.15.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
+ "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.4",
@@ -6772,44 +7074,50 @@
}
},
"node_modules/es-abstract": {
- "version": "1.21.1",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz",
- "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==",
+ "version": "1.22.3",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz",
+ "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==",
"dev": true,
"dependencies": {
+ "array-buffer-byte-length": "^1.0.0",
+ "arraybuffer.prototype.slice": "^1.0.2",
"available-typed-arrays": "^1.0.5",
- "call-bind": "^1.0.2",
+ "call-bind": "^1.0.5",
"es-set-tostringtag": "^2.0.1",
"es-to-primitive": "^1.2.1",
- "function-bind": "^1.1.1",
- "function.prototype.name": "^1.1.5",
- "get-intrinsic": "^1.1.3",
+ "function.prototype.name": "^1.1.6",
+ "get-intrinsic": "^1.2.2",
"get-symbol-description": "^1.0.0",
"globalthis": "^1.0.3",
"gopd": "^1.0.1",
- "has": "^1.0.3",
"has-property-descriptors": "^1.0.0",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3",
- "internal-slot": "^1.0.4",
- "is-array-buffer": "^3.0.1",
+ "hasown": "^2.0.0",
+ "internal-slot": "^1.0.5",
+ "is-array-buffer": "^3.0.2",
"is-callable": "^1.2.7",
"is-negative-zero": "^2.0.2",
"is-regex": "^1.1.4",
"is-shared-array-buffer": "^1.0.2",
"is-string": "^1.0.7",
- "is-typed-array": "^1.1.10",
+ "is-typed-array": "^1.1.12",
"is-weakref": "^1.0.2",
- "object-inspect": "^1.12.2",
+ "object-inspect": "^1.13.1",
"object-keys": "^1.1.1",
"object.assign": "^4.1.4",
- "regexp.prototype.flags": "^1.4.3",
+ "regexp.prototype.flags": "^1.5.1",
+ "safe-array-concat": "^1.0.1",
"safe-regex-test": "^1.0.0",
- "string.prototype.trimend": "^1.0.6",
- "string.prototype.trimstart": "^1.0.6",
+ "string.prototype.trim": "^1.2.8",
+ "string.prototype.trimend": "^1.0.7",
+ "string.prototype.trimstart": "^1.0.7",
+ "typed-array-buffer": "^1.0.0",
+ "typed-array-byte-length": "^1.0.0",
+ "typed-array-byte-offset": "^1.0.0",
"typed-array-length": "^1.0.4",
"unbox-primitive": "^1.0.2",
- "which-typed-array": "^1.1.9"
+ "which-typed-array": "^1.1.13"
},
"engines": {
"node": ">= 0.4"
@@ -6825,26 +7133,26 @@
"dev": true
},
"node_modules/es-set-tostringtag": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
- "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz",
+ "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==",
"dev": true,
"dependencies": {
- "get-intrinsic": "^1.1.3",
- "has": "^1.0.3",
- "has-tostringtag": "^1.0.0"
+ "get-intrinsic": "^1.2.2",
+ "has-tostringtag": "^1.0.0",
+ "hasown": "^2.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-shim-unscopables": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
- "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz",
+ "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==",
"dev": true,
"dependencies": {
- "has": "^1.0.3"
+ "hasown": "^2.0.0"
}
},
"node_modules/es-to-primitive": {
@@ -6888,11 +7196,6 @@
"es6-symbol": "^3.1.1"
}
},
- "node_modules/es6-object-assign": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz",
- "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw="
- },
"node_modules/es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
@@ -7011,27 +7314,28 @@
}
},
"node_modules/eslint": {
- "version": "8.41.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz",
- "integrity": "sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
+ "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.4.0",
- "@eslint/eslintrc": "^2.0.3",
- "@eslint/js": "8.41.0",
- "@humanwhocodes/config-array": "^0.11.8",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.56.0",
+ "@humanwhocodes/config-array": "^0.11.13",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
- "ajv": "^6.10.0",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"debug": "^4.3.2",
"doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^7.2.0",
- "eslint-visitor-keys": "^3.4.1",
- "espree": "^9.5.2",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
"esquery": "^1.4.2",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@@ -7041,7 +7345,6 @@
"globals": "^13.19.0",
"graphemer": "^1.4.0",
"ignore": "^5.2.0",
- "import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"is-path-inside": "^3.0.3",
@@ -7051,9 +7354,8 @@
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
- "optionator": "^0.9.1",
+ "optionator": "^0.9.3",
"strip-ansi": "^6.0.1",
- "strip-json-comments": "^3.1.0",
"text-table": "^0.2.0"
},
"bin": {
@@ -7067,9 +7369,9 @@
}
},
"node_modules/eslint-config-standard": {
- "version": "17.0.0",
- "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz",
- "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==",
+ "version": "17.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz",
+ "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==",
"dev": true,
"funding": [
{
@@ -7085,22 +7387,25 @@
"url": "https://feross.org/support"
}
],
+ "engines": {
+ "node": ">=12.0.0"
+ },
"peerDependencies": {
"eslint": "^8.0.1",
"eslint-plugin-import": "^2.25.2",
- "eslint-plugin-n": "^15.0.0",
+ "eslint-plugin-n": "^15.0.0 || ^16.0.0 ",
"eslint-plugin-promise": "^6.0.0"
}
},
"node_modules/eslint-import-resolver-node": {
- "version": "0.3.7",
- "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz",
- "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==",
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
+ "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
"dev": true,
"dependencies": {
"debug": "^3.2.7",
- "is-core-module": "^2.11.0",
- "resolve": "^1.22.1"
+ "is-core-module": "^2.13.0",
+ "resolve": "^1.22.4"
}
},
"node_modules/eslint-import-resolver-node/node_modules/debug": {
@@ -7113,9 +7418,9 @@
}
},
"node_modules/eslint-module-utils": {
- "version": "2.7.4",
- "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz",
- "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==",
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz",
+ "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==",
"dev": true,
"dependencies": {
"debug": "^3.2.7"
@@ -7159,26 +7464,28 @@
}
},
"node_modules/eslint-plugin-import": {
- "version": "2.27.5",
- "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz",
- "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==",
+ "version": "2.29.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz",
+ "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==",
"dev": true,
"dependencies": {
- "array-includes": "^3.1.6",
- "array.prototype.flat": "^1.3.1",
- "array.prototype.flatmap": "^1.3.1",
+ "array-includes": "^3.1.7",
+ "array.prototype.findlastindex": "^1.2.3",
+ "array.prototype.flat": "^1.3.2",
+ "array.prototype.flatmap": "^1.3.2",
"debug": "^3.2.7",
"doctrine": "^2.1.0",
- "eslint-import-resolver-node": "^0.3.7",
- "eslint-module-utils": "^2.7.4",
- "has": "^1.0.3",
- "is-core-module": "^2.11.0",
+ "eslint-import-resolver-node": "^0.3.9",
+ "eslint-module-utils": "^2.8.0",
+ "hasown": "^2.0.0",
+ "is-core-module": "^2.13.1",
"is-glob": "^4.0.3",
"minimatch": "^3.1.2",
- "object.values": "^1.1.6",
- "resolve": "^1.22.1",
- "semver": "^6.3.0",
- "tsconfig-paths": "^3.14.1"
+ "object.fromentries": "^2.0.7",
+ "object.groupby": "^1.0.1",
+ "object.values": "^1.1.7",
+ "semver": "^6.3.1",
+ "tsconfig-paths": "^3.15.0"
},
"engines": {
"node": ">=4"
@@ -7368,9 +7675,9 @@
}
},
"node_modules/eslint-visitor-keys": {
- "version": "3.4.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
- "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -7447,9 +7754,9 @@
}
},
"node_modules/eslint/node_modules/eslint-scope": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz",
- "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==",
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
"dev": true,
"dependencies": {
"esrecurse": "^4.3.0",
@@ -7587,12 +7894,12 @@
}
},
"node_modules/espree": {
- "version": "9.5.2",
- "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz",
- "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==",
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
"dev": true,
"dependencies": {
- "acorn": "^8.8.0",
+ "acorn": "^8.9.0",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^3.4.1"
},
@@ -8063,9 +8370,9 @@
}
},
"node_modules/ethereumjs-util/node_modules/@types/bn.js": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz",
- "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==",
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.3.tgz",
+ "integrity": "sha512-wT1B4iIO82ecXkdN6waCK8Ou7E71WU+mP1osDA5Q8c6Ur+ozU2vIKUIhSpUr6uE5L2YHocKS1Z2jG2fBC1YVeg==",
"dependencies": {
"@types/node": "*"
}
@@ -8231,16 +8538,16 @@
}
},
"node_modules/expect": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz",
- "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==",
+ "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.5.0",
- "jest-get-type": "^29.4.3",
- "jest-matcher-utils": "^29.5.0",
- "jest-message-util": "^29.5.0",
- "jest-util": "^29.5.0"
+ "@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"
@@ -8410,9 +8717,9 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-glob": {
- "version": "3.2.11",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
- "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+ "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
@@ -8569,33 +8876,20 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
- "node_modules/find-cache-dir": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
- "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
- "dev": true,
- "dependencies": {
- "commondir": "^1.0.1",
- "make-dir": "^3.0.2",
- "pkg-dir": "^4.1.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/avajs/find-cache-dir?sponsor=1"
- }
- },
- "node_modules/find-cache-dir/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==",
+ "node_modules/find-cache-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz",
+ "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==",
"dev": true,
"dependencies": {
- "find-up": "^4.0.0"
+ "common-path-prefix": "^3.0.0",
+ "pkg-dir": "^7.0.0"
},
"engines": {
- "node": ">=8"
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/find-up": {
@@ -8682,16 +8976,16 @@
}
},
"node_modules/fraction.js": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
- "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz",
+ "integrity": "sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==",
"dev": true,
"engines": {
"node": "*"
},
"funding": {
"type": "patreon",
- "url": "https://www.patreon.com/infusion"
+ "url": "https://github.com/sponsors/rawify"
}
},
"node_modules/fresh": {
@@ -8741,20 +9035,23 @@
}
},
"node_modules/function-bind": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
- "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
},
"node_modules/function.prototype.name": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
- "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz",
+ "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.3",
- "es-abstract": "^1.19.0",
- "functions-have-names": "^1.2.2"
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "functions-have-names": "^1.2.3"
},
"engines": {
"node": ">= 0.4"
@@ -8794,21 +9091,22 @@
}
},
"node_modules/get-func-name": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
- "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
+ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
"engines": {
"node": "*"
}
},
"node_modules/get-intrinsic": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
- "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
+ "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==",
"dependencies": {
- "function-bind": "^1.1.1",
- "has": "^1.0.3",
- "has-symbols": "^1.0.3"
+ "function-bind": "^1.1.2",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "hasown": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -8929,28 +9227,29 @@
}
},
"node_modules/globby": {
- "version": "13.1.2",
- "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz",
- "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==",
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz",
+ "integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==",
"dev": true,
"dependencies": {
- "dir-glob": "^3.0.1",
- "fast-glob": "^3.2.11",
- "ignore": "^5.2.0",
- "merge2": "^1.4.1",
- "slash": "^4.0.0"
+ "@sindresorhus/merge-streams": "^1.0.0",
+ "fast-glob": "^3.3.2",
+ "ignore": "^5.2.4",
+ "path-type": "^5.0.0",
+ "slash": "^5.1.0",
+ "unicorn-magic": "^0.1.0"
},
"engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/globby/node_modules/slash": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
- "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==",
+ "node_modules/globby/node_modules/path-type": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz",
+ "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==",
"dev": true,
"engines": {
"node": ">=12"
@@ -8959,6 +9258,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/globby/node_modules/slash": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
+ "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/good-listener": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
@@ -9036,17 +9347,6 @@
"node": ">=6"
}
},
- "node_modules/has": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
- "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
- "dependencies": {
- "function-bind": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4.0"
- }
- },
"node_modules/has-bigints": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
@@ -9079,7 +9379,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
- "dev": true,
"engines": {
"node": ">= 0.4"
},
@@ -9153,10 +9452,21 @@
"minimalistic-assert": "^1.0.1"
}
},
+ "node_modules/hasown": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
+ "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/highlight.js": {
- "version": "11.8.0",
- "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.8.0.tgz",
- "integrity": "sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg==",
+ "version": "11.9.0",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz",
+ "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==",
"engines": {
"node": ">=12.0.0"
}
@@ -9342,9 +9652,9 @@
]
},
"node_modules/ignore": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
- "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
+ "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==",
"dev": true,
"engines": {
"node": ">= 4"
@@ -9430,13 +9740,13 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/internal-slot": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz",
- "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==",
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz",
+ "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==",
"dev": true,
"dependencies": {
- "get-intrinsic": "^1.1.3",
- "has": "^1.0.3",
+ "get-intrinsic": "^1.2.2",
+ "hasown": "^2.0.0",
"side-channel": "^1.0.4"
},
"engines": {
@@ -9476,13 +9786,13 @@
}
},
"node_modules/is-array-buffer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz",
- "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
+ "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.2",
- "get-intrinsic": "^1.1.3",
+ "get-intrinsic": "^1.2.0",
"is-typed-array": "^1.1.10"
},
"funding": {
@@ -9547,11 +9857,11 @@
}
},
"node_modules/is-core-module": {
- "version": "2.11.0",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
- "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
+ "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
"dependencies": {
- "has": "^1.0.3"
+ "hasown": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -9796,15 +10106,11 @@
}
},
"node_modules/is-typed-array": {
- "version": "1.1.10",
- "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz",
- "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==",
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz",
+ "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==",
"dependencies": {
- "available-typed-arrays": "^1.0.5",
- "call-bind": "^1.0.2",
- "for-each": "^0.3.3",
- "gopd": "^1.0.1",
- "has-tostringtag": "^1.0.0"
+ "which-typed-array": "^1.1.11"
},
"engines": {
"node": ">= 0.4"
@@ -9865,33 +10171,48 @@
}
},
"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==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz",
+ "integrity": "sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==",
"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"
+ "semver": "^7.5.4"
},
"engines": {
- "node": ">=8"
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-instrument/node_modules/semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
}
},
"node_modules/istanbul-lib-report": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
- "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==",
+ "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": "^3.0.0",
+ "make-dir": "^4.0.0",
"supports-color": "^7.1.0"
},
"engines": {
- "node": ">=8"
+ "node": ">=10"
}
},
"node_modules/istanbul-lib-report/node_modules/has-flag": {
@@ -9930,9 +10251,9 @@
}
},
"node_modules/istanbul-reports": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz",
- "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==",
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz",
+ "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==",
"dev": true,
"dependencies": {
"html-escaper": "^2.0.0",
@@ -9943,15 +10264,15 @@
}
},
"node_modules/jest": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz",
- "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==",
+ "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.5.0",
- "@jest/types": "^29.5.0",
+ "@jest/core": "^29.7.0",
+ "@jest/types": "^29.6.3",
"import-local": "^3.0.2",
- "jest-cli": "^29.5.0"
+ "jest-cli": "^29.7.0"
},
"bin": {
"jest": "bin/jest.js"
@@ -9969,12 +10290,13 @@
}
},
"node_modules/jest-changed-files": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz",
- "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==",
+ "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": {
@@ -9982,28 +10304,28 @@
}
},
"node_modules/jest-circus": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz",
- "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==",
+ "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.5.0",
- "@jest/expect": "^29.5.0",
- "@jest/test-result": "^29.5.0",
- "@jest/types": "^29.5.0",
+ "@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": "^0.7.0",
+ "dedent": "^1.0.0",
"is-generator-fn": "^2.0.0",
- "jest-each": "^29.5.0",
- "jest-matcher-utils": "^29.5.0",
- "jest-message-util": "^29.5.0",
- "jest-runtime": "^29.5.0",
- "jest-snapshot": "^29.5.0",
- "jest-util": "^29.5.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.5.0",
+ "pretty-format": "^29.7.0",
"pure-rand": "^6.0.0",
"slash": "^3.0.0",
"stack-utils": "^2.0.3"
@@ -10083,22 +10405,21 @@
}
},
"node_modules/jest-cli": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz",
- "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==",
+ "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.5.0",
- "@jest/test-result": "^29.5.0",
- "@jest/types": "^29.5.0",
+ "@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",
- "graceful-fs": "^4.2.9",
"import-local": "^3.0.2",
- "jest-config": "^29.5.0",
- "jest-util": "^29.5.0",
- "jest-validate": "^29.5.0",
- "prompts": "^2.0.1",
+ "jest-config": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
"yargs": "^17.3.1"
},
"bin": {
@@ -10187,31 +10508,31 @@
}
},
"node_modules/jest-config": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz",
- "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==",
+ "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.5.0",
- "@jest/types": "^29.5.0",
- "babel-jest": "^29.5.0",
+ "@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.5.0",
- "jest-environment-node": "^29.5.0",
- "jest-get-type": "^29.4.3",
- "jest-regex-util": "^29.4.3",
- "jest-resolve": "^29.5.0",
- "jest-runner": "^29.5.0",
- "jest-util": "^29.5.0",
- "jest-validate": "^29.5.0",
+ "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.5.0",
+ "pretty-format": "^29.7.0",
"slash": "^3.0.0",
"strip-json-comments": "^3.1.1"
},
@@ -10302,15 +10623,15 @@
}
},
"node_modules/jest-diff": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz",
- "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==",
+ "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.4.3",
- "jest-get-type": "^29.4.3",
- "pretty-format": "^29.5.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"
@@ -10387,9 +10708,9 @@
}
},
"node_modules/jest-docblock": {
- "version": "29.4.3",
- "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz",
- "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==",
+ "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"
@@ -10399,16 +10720,16 @@
}
},
"node_modules/jest-each": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz",
- "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==",
+ "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.5.0",
+ "@jest/types": "^29.6.3",
"chalk": "^4.0.0",
- "jest-get-type": "^29.4.3",
- "jest-util": "^29.5.0",
- "pretty-format": "^29.5.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"
@@ -10485,18 +10806,18 @@
}
},
"node_modules/jest-environment-jsdom": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.5.0.tgz",
- "integrity": "sha512-/KG8yEK4aN8ak56yFVdqFDzKNHgF4BAymCx2LbPNPsUshUlfAl0eX402Xm1pt+eoG9SLZEUVifqXtX8SK74KCw==",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz",
+ "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==",
"dev": true,
"dependencies": {
- "@jest/environment": "^29.5.0",
- "@jest/fake-timers": "^29.5.0",
- "@jest/types": "^29.5.0",
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
"@types/jsdom": "^20.0.0",
"@types/node": "*",
- "jest-mock": "^29.5.0",
- "jest-util": "^29.5.0",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0",
"jsdom": "^20.0.0"
},
"engines": {
@@ -10512,46 +10833,46 @@
}
},
"node_modules/jest-environment-node": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz",
- "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==",
+ "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.5.0",
- "@jest/fake-timers": "^29.5.0",
- "@jest/types": "^29.5.0",
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
"@types/node": "*",
- "jest-mock": "^29.5.0",
- "jest-util": "^29.5.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-get-type": {
- "version": "29.4.3",
- "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz",
- "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==",
+ "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.5.0",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz",
- "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==",
+ "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.5.0",
+ "@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.4.3",
- "jest-util": "^29.5.0",
- "jest-worker": "^29.5.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "jest-worker": "^29.7.0",
"micromatch": "^4.0.4",
"walker": "^1.0.8"
},
@@ -10572,13 +10893,13 @@
}
},
"node_modules/jest-haste-map/node_modules/jest-worker": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz",
- "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==",
+ "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.5.0",
+ "jest-util": "^29.7.0",
"merge-stream": "^2.0.0",
"supports-color": "^8.0.0"
},
@@ -10602,28 +10923,28 @@
}
},
"node_modules/jest-leak-detector": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz",
- "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==",
+ "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.4.3",
- "pretty-format": "^29.5.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": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz",
- "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==",
+ "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.5.0",
- "jest-get-type": "^29.4.3",
- "pretty-format": "^29.5.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"
@@ -10700,18 +11021,18 @@
}
},
"node_modules/jest-message-util": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz",
- "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==",
+ "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.5.0",
+ "@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.5.0",
+ "pretty-format": "^29.7.0",
"slash": "^3.0.0",
"stack-utils": "^2.0.3"
},
@@ -10790,14 +11111,14 @@
}
},
"node_modules/jest-mock": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz",
- "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==",
+ "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.5.0",
+ "@jest/types": "^29.6.3",
"@types/node": "*",
- "jest-util": "^29.5.0"
+ "jest-util": "^29.7.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
@@ -10821,26 +11142,26 @@
}
},
"node_modules/jest-regex-util": {
- "version": "29.4.3",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz",
- "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==",
+ "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.5.0",
- "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz",
- "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==",
+ "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.5.0",
+ "jest-haste-map": "^29.7.0",
"jest-pnp-resolver": "^1.2.2",
- "jest-util": "^29.5.0",
- "jest-validate": "^29.5.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
"resolve": "^1.20.0",
"resolve.exports": "^2.0.0",
"slash": "^3.0.0"
@@ -10850,13 +11171,13 @@
}
},
"node_modules/jest-resolve-dependencies": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz",
- "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==",
+ "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.4.3",
- "jest-snapshot": "^29.5.0"
+ "jest-regex-util": "^29.6.3",
+ "jest-snapshot": "^29.7.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
@@ -10933,30 +11254,30 @@
}
},
"node_modules/jest-runner": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz",
- "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==",
+ "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.5.0",
- "@jest/environment": "^29.5.0",
- "@jest/test-result": "^29.5.0",
- "@jest/transform": "^29.5.0",
- "@jest/types": "^29.5.0",
+ "@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.4.3",
- "jest-environment-node": "^29.5.0",
- "jest-haste-map": "^29.5.0",
- "jest-leak-detector": "^29.5.0",
- "jest-message-util": "^29.5.0",
- "jest-resolve": "^29.5.0",
- "jest-runtime": "^29.5.0",
- "jest-util": "^29.5.0",
- "jest-watcher": "^29.5.0",
- "jest-worker": "^29.5.0",
+ "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"
},
@@ -11023,13 +11344,13 @@
}
},
"node_modules/jest-runner/node_modules/jest-worker": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz",
- "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==",
+ "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.5.0",
+ "jest-util": "^29.7.0",
"merge-stream": "^2.0.0",
"supports-color": "^8.0.0"
},
@@ -11075,31 +11396,31 @@
}
},
"node_modules/jest-runtime": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz",
- "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==",
- "dev": true,
- "dependencies": {
- "@jest/environment": "^29.5.0",
- "@jest/fake-timers": "^29.5.0",
- "@jest/globals": "^29.5.0",
- "@jest/source-map": "^29.4.3",
- "@jest/test-result": "^29.5.0",
- "@jest/transform": "^29.5.0",
- "@jest/types": "^29.5.0",
+ "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.5.0",
- "jest-message-util": "^29.5.0",
- "jest-mock": "^29.5.0",
- "jest-regex-util": "^29.4.3",
- "jest-resolve": "^29.5.0",
- "jest-snapshot": "^29.5.0",
- "jest-util": "^29.5.0",
+ "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"
},
@@ -11178,34 +11499,31 @@
}
},
"node_modules/jest-snapshot": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz",
- "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==",
+ "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/traverse": "^7.7.2",
"@babel/types": "^7.3.3",
- "@jest/expect-utils": "^29.5.0",
- "@jest/transform": "^29.5.0",
- "@jest/types": "^29.5.0",
- "@types/babel__traverse": "^7.0.6",
- "@types/prettier": "^2.1.5",
+ "@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.5.0",
+ "expect": "^29.7.0",
"graceful-fs": "^4.2.9",
- "jest-diff": "^29.5.0",
- "jest-get-type": "^29.4.3",
- "jest-matcher-utils": "^29.5.0",
- "jest-message-util": "^29.5.0",
- "jest-util": "^29.5.0",
+ "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.5.0",
- "semver": "^7.3.5"
+ "pretty-format": "^29.7.0",
+ "semver": "^7.5.3"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
@@ -11270,9 +11588,9 @@
}
},
"node_modules/jest-snapshot/node_modules/semver": {
- "version": "7.3.8",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
- "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
@@ -11297,12 +11615,12 @@
}
},
"node_modules/jest-util": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz",
- "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==",
+ "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.5.0",
+ "@jest/types": "^29.6.3",
"@types/node": "*",
"chalk": "^4.0.0",
"ci-info": "^3.2.0",
@@ -11384,17 +11702,17 @@
}
},
"node_modules/jest-validate": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz",
- "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==",
+ "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.5.0",
+ "@jest/types": "^29.6.3",
"camelcase": "^6.2.0",
"chalk": "^4.0.0",
- "jest-get-type": "^29.4.3",
+ "jest-get-type": "^29.6.3",
"leven": "^3.1.0",
- "pretty-format": "^29.5.0"
+ "pretty-format": "^29.7.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
@@ -11483,18 +11801,18 @@
}
},
"node_modules/jest-watcher": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz",
- "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==",
+ "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.5.0",
- "@jest/types": "^29.5.0",
+ "@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.5.0",
+ "jest-util": "^29.7.0",
"string-length": "^4.0.1"
},
"engines": {
@@ -11610,18 +11928,18 @@
}
},
"node_modules/jiti": {
- "version": "1.18.2",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz",
- "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==",
+ "version": "1.21.0",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz",
+ "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==",
"dev": true,
"bin": {
"jiti": "bin/jiti.js"
}
},
"node_modules/jquery": {
- "version": "3.7.0",
- "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz",
- "integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ=="
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
+ "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
},
"node_modules/jquery-mousewheel": {
"version": "3.1.13",
@@ -11879,9 +12197,9 @@
}
},
"node_modules/keyv": {
- "version": "4.5.2",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz",
- "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==",
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"dependencies": {
"json-buffer": "3.0.1"
}
@@ -11909,15 +12227,6 @@
"node": ">=6"
}
},
- "node_modules/klona": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz",
- "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==",
- "dev": true,
- "engines": {
- "node": ">= 8"
- }
- },
"node_modules/level-codec": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/level-codec/-/level-codec-7.0.1.tgz",
@@ -12250,35 +12559,42 @@
"integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU="
},
"node_modules/luxon": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz",
- "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==",
+ "version": "3.4.4",
+ "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz",
+ "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==",
"engines": {
"node": ">=12"
}
},
"node_modules/make-dir": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
- "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "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": "^6.0.0"
+ "semver": "^7.5.3"
},
"engines": {
- "node": ">=8"
+ "node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/make-error": {
- "version": "1.3.6",
- "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
- "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "node_modules/make-dir/node_modules/semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
- "optional": true,
- "peer": true
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
},
"node_modules/makeerror": {
"version": "1.0.12",
@@ -12431,6 +12747,11 @@
"node": ">= 0.6"
}
},
+ "node_modules/micro-ftch": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/micro-ftch/-/micro-ftch-0.3.1.tgz",
+ "integrity": "sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg=="
+ },
"node_modules/micromatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
@@ -12517,9 +12838,9 @@
}
},
"node_modules/mini-css-extract-plugin": {
- "version": "2.7.6",
- "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz",
- "integrity": "sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==",
+ "version": "2.7.7",
+ "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.7.tgz",
+ "integrity": "sha512-+0n11YGyRavUR3IlaOzJ0/4Il1avMvJ1VJfhWfCn24ITQXhRr1gghbhhrda6tgtNcpZaWKdSuwKq20Jb7fnlyw==",
"dev": true,
"dependencies": {
"schema-utils": "^4.0.0"
@@ -12585,9 +12906,9 @@
}
},
"node_modules/mixpanel-browser": {
- "version": "2.47.0",
- "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.47.0.tgz",
- "integrity": "sha512-Ldrva0fRBEIFWmEibBQO1PulfpJVF3pf28Guk09lDirDaSQqqU/xs9zQLwN2rL5VwVtsP1aD3JaCgaa98EjojQ=="
+ "version": "2.48.1",
+ "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.48.1.tgz",
+ "integrity": "sha512-vXTuUzZMg+ht7sRqyjtc3dUDy/81Z/H6FLFgFkUZJqKFaAqcx1JSXmOdY/2kmsxCkUdy5JN5zW9m9TMCk+rxGQ=="
},
"node_modules/mkdirp": {
"version": "3.0.1",
@@ -12621,9 +12942,9 @@
"integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw=="
},
"node_modules/moment": {
- "version": "2.29.4",
- "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
- "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
+ "version": "2.30.1",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
+ "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"engines": {
"node": "*"
}
@@ -12683,9 +13004,9 @@
"integrity": "sha1-TzFS4JVA/eKMdvRLGbvNHVpCR40="
},
"node_modules/nanoid": {
- "version": "3.3.6",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
- "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true,
"funding": [
{
@@ -12739,9 +13060,9 @@
"integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA=="
},
"node_modules/node-fetch": {
- "version": "2.6.7",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
- "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
@@ -12793,9 +13114,9 @@
"dev": true
},
"node_modules/node-releases": {
- "version": "2.0.10",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
- "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w=="
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
+ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw=="
},
"node_modules/normalize-path": {
"version": "3.0.0",
@@ -12899,9 +13220,9 @@
}
},
"node_modules/object-inspect": {
- "version": "1.12.3",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
- "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
+ "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -12933,7 +13254,6 @@
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
"integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
- "dev": true,
"dependencies": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.4",
@@ -12947,15 +13267,44 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/object.fromentries": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz",
+ "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.groupby": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz",
+ "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "get-intrinsic": "^1.2.1"
+ }
+ },
"node_modules/object.values": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz",
- "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==",
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz",
+ "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
},
"engines": {
"node": ">= 0.4"
@@ -13007,17 +13356,17 @@
}
},
"node_modules/optionator": {
- "version": "0.9.1",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
- "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
+ "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
"dev": true,
"dependencies": {
+ "@aashutoshrathi/word-wrap": "^1.2.3",
"deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6",
"levn": "^0.4.1",
"prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.3"
+ "type-check": "^0.4.0"
},
"engines": {
"node": ">= 0.8.0"
@@ -13243,51 +13592,148 @@
"resolved": "../../../deps/phoenix_html",
"link": true
},
- "node_modules/photoswipe": {
- "version": "5.3.7",
- "resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.3.7.tgz",
- "integrity": "sha512-zsyLsTTLFrj0XR1m4/hO7qNooboFKUrDy+Zt5i2d6qjFPAtBjzaj/Xtydso4uxzcXpcqbTmyxDibb3BcSISseg==",
+ "node_modules/photoswipe": {
+ "version": "5.4.3",
+ "resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.4.3.tgz",
+ "integrity": "sha512-9UC6oJBK4oXFZ5HcdlcvGkfEHsVrmE4csUdCQhEjHYb3PvPLO3PG7UhnPuOgjxwmhq5s17Un5NUdum01LgBDng==",
+ "engines": {
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
+ "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pikaday": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/pikaday/-/pikaday-1.8.2.tgz",
+ "integrity": "sha512-TNtsE+34BIax3WtkB/qqu5uepV1McKYEgvL3kWzU7aqPCpMEN6rBF3AOwu4WCwAealWlBGobXny/9kJb49C1ew=="
+ },
+ "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": "7.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz",
+ "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==",
+ "dev": true,
+ "dependencies": {
+ "find-up": "^6.3.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/find-up": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz",
+ "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^7.1.0",
+ "path-exists": "^5.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/locate-path": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz",
+ "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^6.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-limit": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
+ "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^1.0.0"
+ },
"engines": {
- "node": ">= 0.12.0"
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/picocolors": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
- "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
- },
- "node_modules/picomatch": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
- "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
+ "node_modules/pkg-dir/node_modules/p-locate": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz",
+ "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==",
"dev": true,
+ "dependencies": {
+ "p-limit": "^4.0.0"
+ },
"engines": {
- "node": ">=8.6"
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/pify": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
- "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+ "node_modules/pkg-dir/node_modules/path-exists": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz",
+ "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==",
+ "dev": true,
"engines": {
- "node": ">=4"
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
},
- "node_modules/pikaday": {
- "version": "1.8.2",
- "resolved": "https://registry.npmjs.org/pikaday/-/pikaday-1.8.2.tgz",
- "integrity": "sha512-TNtsE+34BIax3WtkB/qqu5uepV1McKYEgvL3kWzU7aqPCpMEN6rBF3AOwu4WCwAealWlBGobXny/9kJb49C1ew=="
- },
- "node_modules/pirates": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
- "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==",
+ "node_modules/pkg-dir/node_modules/yocto-queue": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
+ "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
"dev": true,
"engines": {
- "node": ">= 6"
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pngjs": {
@@ -13309,9 +13755,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.23",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz",
- "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==",
+ "version": "8.4.33",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
+ "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
"dev": true,
"funding": [
{
@@ -13328,7 +13774,7 @@
}
],
"dependencies": {
- "nanoid": "^3.3.6",
+ "nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
@@ -13337,14 +13783,17 @@
}
},
"node_modules/postcss-calc": {
- "version": "8.2.4",
- "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz",
- "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==",
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz",
+ "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==",
"dev": true,
"dependencies": {
- "postcss-selector-parser": "^6.0.9",
+ "postcss-selector-parser": "^6.0.11",
"postcss-value-parser": "^4.2.0"
},
+ "engines": {
+ "node": "^14 || ^16 || >=18.0"
+ },
"peerDependencies": {
"postcss": "^8.2.2"
}
@@ -13432,15 +13881,14 @@
}
},
"node_modules/postcss-loader": {
- "version": "7.3.0",
- "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.0.tgz",
- "integrity": "sha512-qLAFjvR2BFNz1H930P7mj1iuWJFjGey/nVhimfOAAQ1ZyPpcClAxP8+A55Sl8mBvM+K2a9Pjgdj10KpANWrNfw==",
+ "version": "7.3.4",
+ "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz",
+ "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==",
"dev": true,
"dependencies": {
- "cosmiconfig": "^8.1.3",
- "jiti": "^1.18.2",
- "klona": "^2.0.6",
- "semver": "^7.3.8"
+ "cosmiconfig": "^8.3.5",
+ "jiti": "^1.20.0",
+ "semver": "^7.5.4"
},
"engines": {
"node": ">= 14.15.0"
@@ -13455,9 +13903,9 @@
}
},
"node_modules/postcss-loader/node_modules/semver": {
- "version": "7.3.8",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
- "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
@@ -13486,9 +13934,9 @@
}
},
"node_modules/postcss-merge-rules": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.0.tgz",
- "integrity": "sha512-rCXkklftzEkniyv3f4mRCQzxD6oE4Quyh61uyWTUbCJ26Pv2hoz+fivJSsSBWxDBeScR4fKCfF3HHTcD7Ybqnw==",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.1.tgz",
+ "integrity": "sha512-a4tlmJIQo9SCjcfiCcCMg/ZCEe0XTkl/xK0XHBs955GWg9xDX3NwP9pwZ78QUOWB8/0XCjZeJn98Dae0zg6AAw==",
"dev": true,
"dependencies": {
"browserslist": "^4.21.4",
@@ -13580,9 +14028,9 @@
}
},
"node_modules/postcss-modules-local-by-default": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz",
- "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz",
+ "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==",
"dev": true,
"dependencies": {
"icss-utils": "^5.0.0",
@@ -13597,9 +14045,9 @@
}
},
"node_modules/postcss-modules-scope": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz",
- "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.0.tgz",
+ "integrity": "sha512-SaIbK8XW+MZbd0xHPf7kdfA/3eOt7vxJ72IRecn3EzuZVLr1r0orzf0MX/pN8m+NMDoo6X/SQd8oeKqGZd8PXg==",
"dev": true,
"dependencies": {
"postcss-selector-parser": "^6.0.4"
@@ -13807,9 +14255,9 @@
}
},
"node_modules/postcss-selector-parser": {
- "version": "6.0.10",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
- "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
+ "version": "6.0.13",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
+ "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
"dev": true,
"dependencies": {
"cssesc": "^3.0.0",
@@ -13882,12 +14330,12 @@
}
},
"node_modules/pretty-format": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz",
- "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==",
+ "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.4.3",
+ "@jest/schemas": "^29.6.3",
"ansi-styles": "^5.0.0",
"react-is": "^18.0.0"
},
@@ -14019,9 +14467,9 @@
}
},
"node_modules/pure-rand": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.0.tgz",
- "integrity": "sha512-rLSBxJjP+4DQOgcJAx6RZHT2he2pkhQdSnofG5VWyVl6GRq/K02ISOuOLcsMOrtKDIJb8JN2zm3FFzWNbezdPw==",
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.3.tgz",
+ "integrity": "sha512-KddyFewCsO0j3+np81IQ+SweXLDnDQTs5s67BOnrYmYe/yNmUhttQyGsYzy8yUnoljGAQ9sl38YB4vH8ur7Y+w==",
"dev": true,
"funding": [
{
@@ -14222,15 +14670,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/querystring": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
- "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
- "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.",
- "engines": {
- "node": ">=0.4.x"
- }
- },
"node_modules/querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
@@ -14340,9 +14779,9 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/readable-stream": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
- "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -14382,12 +14821,9 @@
"integrity": "sha512-Mb2WZ2bJF597exiqX7owBzrqJ74DHLK3yOQjCyPAaNifRncE8OD0wFIuoMhXxTnHK07+8zZ2SJEKy/qtiyR7vw=="
},
"node_modules/redux": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
- "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
- "dependencies": {
- "@babel/runtime": "^7.9.2"
- }
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+ "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
},
"node_modules/regenerate": {
"version": "1.4.2",
@@ -14413,23 +14849,23 @@
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
},
"node_modules/regenerator-transform": {
- "version": "0.15.1",
- "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz",
- "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==",
+ "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/regexp.prototype.flags": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
- "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
+ "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.3",
- "functions-have-names": "^1.2.2"
+ "define-properties": "^1.2.0",
+ "set-function-name": "^2.0.0"
},
"engines": {
"node": ">= 0.4"
@@ -14560,11 +14996,11 @@
"dev": true
},
"node_modules/resolve": {
- "version": "1.22.1",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
- "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+ "version": "1.22.8",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+ "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"dependencies": {
- "is-core-module": "^2.9.0",
+ "is-core-module": "^2.13.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
@@ -14611,9 +15047,9 @@
}
},
"node_modules/resolve.exports": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.1.tgz",
- "integrity": "sha512-OEJWVeimw8mgQuj3HfkNl4KqRevH7lzeQNaWRPfx0PPse7Jk6ozcsG4FKVgtzDsC1KUF+YlTHh17NcgHOPykLw==",
+ "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"
@@ -14711,6 +15147,30 @@
"resolved": "https://registry.npmjs.org/rustbn.js/-/rustbn.js-0.2.0.tgz",
"integrity": "sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA=="
},
+ "node_modules/safe-array-concat": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz",
+ "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.2.1",
+ "has-symbols": "^1.0.3",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">=0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-array-concat/node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true
+ },
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
@@ -14745,9 +15205,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/sass": {
- "version": "1.62.1",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.62.1.tgz",
- "integrity": "sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A==",
+ "version": "1.69.7",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz",
+ "integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==",
"dev": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
@@ -14762,32 +15222,27 @@
}
},
"node_modules/sass-loader": {
- "version": "13.3.0",
- "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.0.tgz",
- "integrity": "sha512-LeWNswSEujsZnwdn9AuA+Q5wZEAFlU+eORQsDKp35OtGAfFxYxpfk/Ylon+TGGkazSqxi2EHDTqdr3di8r7nCg==",
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.0.0.tgz",
+ "integrity": "sha512-oceP9wWbep/yRJ2+sMbCzk0UsXsDzdNis+N8nu9i5GwPXjy6v3DNB6TqfJLSpPO9k4+B8x8p/CEgjA9ZLkoLug==",
"dev": true,
"dependencies": {
- "klona": "^2.0.6",
"neo-async": "^2.6.2"
},
"engines": {
- "node": ">= 14.15.0"
+ "node": ">= 18.12.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
- "fibers": ">= 3.1.0",
"node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
"sass": "^1.3.0",
"sass-embedded": "*",
"webpack": "^5.0.0"
},
"peerDependenciesMeta": {
- "fibers": {
- "optional": true
- },
"node-sass": {
"optional": true
},
@@ -14821,15 +15276,15 @@
}
},
"node_modules/schema-utils": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz",
- "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz",
+ "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.9",
- "ajv": "^8.8.0",
+ "ajv": "^8.9.0",
"ajv-formats": "^2.1.1",
- "ajv-keywords": "^5.0.0"
+ "ajv-keywords": "^5.1.0"
},
"engines": {
"node": ">= 12.13.0"
@@ -14911,9 +15366,9 @@
}
},
"node_modules/semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
}
@@ -14960,9 +15415,9 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/serialize-javascript": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
- "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==",
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
+ "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
"dev": true,
"dependencies": {
"randombytes": "^2.1.0"
@@ -15002,6 +15457,34 @@
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
+ "node_modules/set-function-length": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz",
+ "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==",
+ "dependencies": {
+ "define-data-property": "^1.1.1",
+ "get-intrinsic": "^1.2.1",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-function-name": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz",
+ "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==",
+ "dev": true,
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "functions-have-names": "^1.2.3",
+ "has-property-descriptors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/set-immediate-shim": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
@@ -15321,29 +15804,46 @@
"node": ">=8"
}
},
+ "node_modules/string.prototype.trim": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz",
+ "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/string.prototype.trimend": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
- "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz",
+ "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/string.prototype.trimstart": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
- "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz",
+ "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -15404,9 +15904,9 @@
}
},
"node_modules/style-loader": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz",
- "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==",
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz",
+ "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==",
"dev": true,
"engines": {
"node": ">= 12.13.0"
@@ -15600,9 +16100,9 @@
}
},
"node_modules/sweetalert2": {
- "version": "11.7.5",
- "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.7.5.tgz",
- "integrity": "sha512-RBPKdK8Uf9/bz9r4vy7x6sGqf0MioSXt1po1lAwwcl3AwbjHVTc5S0yud4ZJKU1EhvJFVDrFoqIpHwWERwZJEA==",
+ "version": "11.10.3",
+ "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.3.tgz",
+ "integrity": "sha512-mZYtQR7v+khyEruq0SsVUa6XIdI9Aue8s2XAIpAwdlLN1T0w7mxKEjyubiBZ3/bLbHC/wGS4wNABvXWubCizvA==",
"funding": {
"type": "individual",
"url": "https://github.com/sponsors/limonte"
@@ -15859,70 +16359,14 @@
"node": ">=12"
}
},
- "node_modules/ts-node": {
- "version": "10.9.1",
- "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
- "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
- "dev": true,
- "optional": true,
- "peer": true,
- "dependencies": {
- "@cspotcode/source-map-support": "^0.8.0",
- "@tsconfig/node10": "^1.0.7",
- "@tsconfig/node12": "^1.0.7",
- "@tsconfig/node14": "^1.0.0",
- "@tsconfig/node16": "^1.0.2",
- "acorn": "^8.4.1",
- "acorn-walk": "^8.1.1",
- "arg": "^4.1.0",
- "create-require": "^1.1.0",
- "diff": "^4.0.1",
- "make-error": "^1.1.1",
- "v8-compile-cache-lib": "^3.0.1",
- "yn": "3.1.1"
- },
- "bin": {
- "ts-node": "dist/bin.js",
- "ts-node-cwd": "dist/bin-cwd.js",
- "ts-node-esm": "dist/bin-esm.js",
- "ts-node-script": "dist/bin-script.js",
- "ts-node-transpile-only": "dist/bin-transpile.js",
- "ts-script": "dist/bin-script-deprecated.js"
- },
- "peerDependencies": {
- "@swc/core": ">=1.2.50",
- "@swc/wasm": ">=1.2.50",
- "@types/node": "*",
- "typescript": ">=2.7"
- },
- "peerDependenciesMeta": {
- "@swc/core": {
- "optional": true
- },
- "@swc/wasm": {
- "optional": true
- }
- }
- },
- "node_modules/ts-node/node_modules/acorn-walk": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
- "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
- "dev": true,
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">=0.4.0"
- }
- },
"node_modules/tsconfig-paths": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
- "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
+ "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==",
"dev": true,
"dependencies": {
"@types/json5": "^0.0.29",
- "json5": "^1.0.1",
+ "json5": "^1.0.2",
"minimist": "^1.2.6",
"strip-bom": "^3.0.0"
}
@@ -16018,6 +16462,57 @@
"node": ">= 0.6"
}
},
+ "node_modules/typed-array-buffer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz",
+ "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.2.1",
+ "is-typed-array": "^1.1.10"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/typed-array-byte-length": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz",
+ "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "has-proto": "^1.0.1",
+ "is-typed-array": "^1.1.10"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-byte-offset": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz",
+ "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==",
+ "dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "has-proto": "^1.0.1",
+ "is-typed-array": "^1.1.10"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/typed-array-length": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
@@ -16040,21 +16535,6 @@
"is-typedarray": "^1.0.0"
}
},
- "node_modules/typescript": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.3.tgz",
- "integrity": "sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA==",
- "dev": true,
- "optional": true,
- "peer": true,
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=12.20"
- }
- },
"node_modules/ultron": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
@@ -16115,6 +16595,18 @@
"node": ">=4"
}
},
+ "node_modules/unicorn-magic": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz",
+ "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
@@ -16132,9 +16624,9 @@
}
},
"node_modules/update-browserslist-db": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
- "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
+ "version": "1.0.13",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
+ "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
"funding": [
{
"type": "opencollective",
@@ -16143,6 +16635,10 @@
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
@@ -16150,7 +16646,7 @@
"picocolors": "^1.0.0"
},
"bin": {
- "browserslist-lint": "cli.js"
+ "update-browserslist-db": "cli.js"
},
"peerDependencies": {
"browserslist": ">= 4.21.0"
@@ -16170,12 +16666,12 @@
"integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ=="
},
"node_modules/url": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
- "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+ "version": "0.11.3",
+ "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz",
+ "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==",
"dependencies": {
- "punycode": "1.3.2",
- "querystring": "0.2.0"
+ "punycode": "^1.4.1",
+ "qs": "^6.11.2"
}
},
"node_modules/url-parse": {
@@ -16194,9 +16690,23 @@
"integrity": "sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg=="
},
"node_modules/url/node_modules/punycode": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
- "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="
+ },
+ "node_modules/url/node_modules/qs": {
+ "version": "6.11.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
+ "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==",
+ "dependencies": {
+ "side-channel": "^1.0.4"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
},
"node_modules/utf-8-validate": {
"version": "5.0.7",
@@ -16249,14 +16759,6 @@
"uuid": "bin/uuid"
}
},
- "node_modules/v8-compile-cache-lib": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
- "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
- "dev": true,
- "optional": true,
- "peer": true
- },
"node_modules/v8-to-istanbul": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz",
@@ -16298,9 +16800,9 @@
}
},
"node_modules/viewerjs": {
- "version": "1.11.3",
- "resolved": "https://registry.npmjs.org/viewerjs/-/viewerjs-1.11.3.tgz",
- "integrity": "sha512-efG3U61Umuj/1x6JAtdvnY9m407C/RkrkFilsMcLEWKDivpjNU3/FeL+feCY1Vkur9aQeBJ+z6K4CCPP7hv6vA=="
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/viewerjs/-/viewerjs-1.11.6.tgz",
+ "integrity": "sha512-TlhdSp2oEOLFXvEp4psKaeTjR5zBjTRcM/sHUN8PkV1UWuY8HKC8n7GaVdW5Xqnwdr/F1OmzLik1QwDjI4w/nw=="
},
"node_modules/w3c-hr-time": {
"version": "1.0.2",
@@ -16346,27 +16848,27 @@
}
},
"node_modules/web3": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3/-/web3-1.10.0.tgz",
- "integrity": "sha512-YfKY9wSkGcM8seO+daR89oVTcbu18NsVfvOngzqMYGUU0pPSQmE57qQDvQzUeoIOHAnXEBNzrhjQJmm8ER0rng==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3/-/web3-1.10.3.tgz",
+ "integrity": "sha512-DgUdOOqC/gTqW+VQl1EdPxrVRPB66xVNtuZ5KD4adVBtko87hkgM8BTZ0lZ8IbUfnQk6DyjcDujMiH3oszllAw==",
"hasInstallScript": true,
"dependencies": {
- "web3-bzz": "1.10.0",
- "web3-core": "1.10.0",
- "web3-eth": "1.10.0",
- "web3-eth-personal": "1.10.0",
- "web3-net": "1.10.0",
- "web3-shh": "1.10.0",
- "web3-utils": "1.10.0"
+ "web3-bzz": "1.10.3",
+ "web3-core": "1.10.3",
+ "web3-eth": "1.10.3",
+ "web3-eth-personal": "1.10.3",
+ "web3-net": "1.10.3",
+ "web3-shh": "1.10.3",
+ "web3-utils": "1.10.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/web3-bzz": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.10.0.tgz",
- "integrity": "sha512-o9IR59io3pDUsXTsps5pO5hW1D5zBmg46iNc2t4j2DkaYHNdDLwk2IP9ukoM2wg47QILfPEJYzhTfkS/CcX0KA==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.10.3.tgz",
+ "integrity": "sha512-XDIRsTwekdBXtFytMpHBuun4cK4x0ZMIDXSoo1UVYp+oMyZj07c7gf7tNQY5qZ/sN+CJIas4ilhN25VJcjSijQ==",
"hasInstallScript": true,
"dependencies": {
"@types/node": "^12.12.6",
@@ -16383,53 +16885,53 @@
"integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="
},
"node_modules/web3-core": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.10.0.tgz",
- "integrity": "sha512-fWySwqy2hn3TL89w5TM8wXF1Z2Q6frQTKHWmP0ppRQorEK8NcHJRfeMiv/mQlSKoTS1F6n/nv2uyZsixFycjYQ==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.10.3.tgz",
+ "integrity": "sha512-Vbk0/vUNZxJlz3RFjAhNNt7qTpX8yE3dn3uFxfX5OHbuon5u65YEOd3civ/aQNW745N0vGUlHFNxxmn+sG9DIw==",
"dependencies": {
"@types/bn.js": "^5.1.1",
"@types/node": "^12.12.6",
"bignumber.js": "^9.0.0",
- "web3-core-helpers": "1.10.0",
- "web3-core-method": "1.10.0",
- "web3-core-requestmanager": "1.10.0",
- "web3-utils": "1.10.0"
+ "web3-core-helpers": "1.10.3",
+ "web3-core-method": "1.10.3",
+ "web3-core-requestmanager": "1.10.3",
+ "web3-utils": "1.10.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/web3-core-helpers": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.10.0.tgz",
- "integrity": "sha512-pIxAzFDS5vnbXvfvLSpaA1tfRykAe9adw43YCKsEYQwH0gCLL0kMLkaCX3q+Q8EVmAh+e1jWL/nl9U0de1+++g==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.10.3.tgz",
+ "integrity": "sha512-Yv7dQC3B9ipOc5sWm3VAz1ys70Izfzb8n9rSiQYIPjpqtJM+3V4EeK6ghzNR6CO2es0+Yu9CtCkw0h8gQhrTxA==",
"dependencies": {
- "web3-eth-iban": "1.10.0",
- "web3-utils": "1.10.0"
+ "web3-eth-iban": "1.10.3",
+ "web3-utils": "1.10.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/web3-core-method": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.10.0.tgz",
- "integrity": "sha512-4R700jTLAMKDMhQ+nsVfIXvH6IGJlJzGisIfMKWAIswH31h5AZz7uDUW2YctI+HrYd+5uOAlS4OJeeT9bIpvkA==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.10.3.tgz",
+ "integrity": "sha512-VZ/Dmml4NBmb0ep5PTSg9oqKoBtG0/YoMPei/bq/tUdlhB2dMB79sbeJPwx592uaV0Vpk7VltrrrBv5hTM1y4Q==",
"dependencies": {
"@ethersproject/transactions": "^5.6.2",
- "web3-core-helpers": "1.10.0",
- "web3-core-promievent": "1.10.0",
- "web3-core-subscriptions": "1.10.0",
- "web3-utils": "1.10.0"
+ "web3-core-helpers": "1.10.3",
+ "web3-core-promievent": "1.10.3",
+ "web3-core-subscriptions": "1.10.3",
+ "web3-utils": "1.10.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/web3-core-promievent": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.10.0.tgz",
- "integrity": "sha512-68N7k5LWL5R38xRaKFrTFT2pm2jBNFaM4GioS00YjAKXRQ3KjmhijOMG3TICz6Aa5+6GDWYelDNx21YAeZ4YTg==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.10.3.tgz",
+ "integrity": "sha512-HgjY+TkuLm5uTwUtaAfkTgRx/NzMxvVradCi02gy17NxDVdg/p6svBHcp037vcNpkuGeFznFJgULP+s2hdVgUQ==",
"dependencies": {
"eventemitter3": "4.0.4"
},
@@ -16438,36 +16940,36 @@
}
},
"node_modules/web3-core-requestmanager": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.10.0.tgz",
- "integrity": "sha512-3z/JKE++Os62APml4dvBM+GAuId4h3L9ckUrj7ebEtS2AR0ixyQPbrBodgL91Sv7j7cQ3Y+hllaluqjguxvSaQ==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.10.3.tgz",
+ "integrity": "sha512-VT9sKJfgM2yBOIxOXeXiDuFMP4pxzF6FT+y8KTLqhDFHkbG3XRe42Vm97mB/IvLQCJOmokEjl3ps8yP1kbggyw==",
"dependencies": {
"util": "^0.12.5",
- "web3-core-helpers": "1.10.0",
- "web3-providers-http": "1.10.0",
- "web3-providers-ipc": "1.10.0",
- "web3-providers-ws": "1.10.0"
+ "web3-core-helpers": "1.10.3",
+ "web3-providers-http": "1.10.3",
+ "web3-providers-ipc": "1.10.3",
+ "web3-providers-ws": "1.10.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/web3-core-subscriptions": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.10.0.tgz",
- "integrity": "sha512-HGm1PbDqsxejI075gxBc5OSkwymilRWZufIy9zEpnWKNmfbuv5FfHgW1/chtJP6aP3Uq2vHkvTDl3smQBb8l+g==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.10.3.tgz",
+ "integrity": "sha512-KW0Mc8sgn70WadZu7RjQ4H5sNDJ5Lx8JMI3BWos+f2rW0foegOCyWhRu33W1s6ntXnqeBUw5rRCXZRlA3z+HNA==",
"dependencies": {
"eventemitter3": "4.0.4",
- "web3-core-helpers": "1.10.0"
+ "web3-core-helpers": "1.10.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/web3-core/node_modules/@types/bn.js": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz",
- "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==",
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.3.tgz",
+ "integrity": "sha512-wT1B4iIO82ecXkdN6waCK8Ou7E71WU+mP1osDA5Q8c6Ur+ozU2vIKUIhSpUr6uE5L2YHocKS1Z2jG2fBC1YVeg==",
"dependencies": {
"@types/node": "*"
}
@@ -16478,54 +16980,54 @@
"integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="
},
"node_modules/web3-eth": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.10.0.tgz",
- "integrity": "sha512-Z5vT6slNMLPKuwRyKGbqeGYC87OAy8bOblaqRTgg94CXcn/mmqU7iPIlG4506YdcdK3x6cfEDG7B6w+jRxypKA==",
- "dependencies": {
- "web3-core": "1.10.0",
- "web3-core-helpers": "1.10.0",
- "web3-core-method": "1.10.0",
- "web3-core-subscriptions": "1.10.0",
- "web3-eth-abi": "1.10.0",
- "web3-eth-accounts": "1.10.0",
- "web3-eth-contract": "1.10.0",
- "web3-eth-ens": "1.10.0",
- "web3-eth-iban": "1.10.0",
- "web3-eth-personal": "1.10.0",
- "web3-net": "1.10.0",
- "web3-utils": "1.10.0"
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.10.3.tgz",
+ "integrity": "sha512-Uk1U2qGiif2mIG8iKu23/EQJ2ksB1BQXy3wF3RvFuyxt8Ft9OEpmGlO7wOtAyJdoKzD5vcul19bJpPcWSAYZhA==",
+ "dependencies": {
+ "web3-core": "1.10.3",
+ "web3-core-helpers": "1.10.3",
+ "web3-core-method": "1.10.3",
+ "web3-core-subscriptions": "1.10.3",
+ "web3-eth-abi": "1.10.3",
+ "web3-eth-accounts": "1.10.3",
+ "web3-eth-contract": "1.10.3",
+ "web3-eth-ens": "1.10.3",
+ "web3-eth-iban": "1.10.3",
+ "web3-eth-personal": "1.10.3",
+ "web3-net": "1.10.3",
+ "web3-utils": "1.10.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/web3-eth-abi": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.10.0.tgz",
- "integrity": "sha512-cwS+qRBWpJ43aI9L3JS88QYPfFcSJJ3XapxOQ4j40v6mk7ATpA8CVK1vGTzpihNlOfMVRBkR95oAj7oL6aiDOg==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.10.3.tgz",
+ "integrity": "sha512-O8EvV67uhq0OiCMekqYsDtb6FzfYzMXT7VMHowF8HV6qLZXCGTdB/NH4nJrEh2mFtEwVdS6AmLFJAQd2kVyoMQ==",
"dependencies": {
"@ethersproject/abi": "^5.6.3",
- "web3-utils": "1.10.0"
+ "web3-utils": "1.10.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/web3-eth-accounts": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.10.0.tgz",
- "integrity": "sha512-wiq39Uc3mOI8rw24wE2n15hboLE0E9BsQLdlmsL4Zua9diDS6B5abXG0XhFcoNsXIGMWXVZz4TOq3u4EdpXF/Q==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.10.3.tgz",
+ "integrity": "sha512-8MipGgwusDVgn7NwKOmpeo3gxzzd+SmwcWeBdpXknuyDiZSQy9tXe+E9LeFGrmys/8mLLYP79n3jSbiTyv+6pQ==",
"dependencies": {
- "@ethereumjs/common": "2.5.0",
- "@ethereumjs/tx": "3.3.2",
+ "@ethereumjs/common": "2.6.5",
+ "@ethereumjs/tx": "3.5.2",
+ "@ethereumjs/util": "^8.1.0",
"eth-lib": "0.2.8",
- "ethereumjs-util": "^7.1.5",
"scrypt-js": "^3.0.1",
"uuid": "^9.0.0",
- "web3-core": "1.10.0",
- "web3-core-helpers": "1.10.0",
- "web3-core-method": "1.10.0",
- "web3-utils": "1.10.0"
+ "web3-core": "1.10.3",
+ "web3-core-helpers": "1.10.3",
+ "web3-core-method": "1.10.3",
+ "web3-utils": "1.10.3"
},
"engines": {
"node": ">=8.0.0"
@@ -16547,80 +17049,84 @@
}
},
"node_modules/web3-eth-accounts/node_modules/uuid": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
- "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/web3-eth-contract": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.10.0.tgz",
- "integrity": "sha512-MIC5FOzP/+2evDksQQ/dpcXhSqa/2hFNytdl/x61IeWxhh6vlFeSjq0YVTAyIzdjwnL7nEmZpjfI6y6/Ufhy7w==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.10.3.tgz",
+ "integrity": "sha512-Y2CW61dCCyY4IoUMD4JsEQWrILX4FJWDWC/Txx/pr3K/+fGsBGvS9kWQN5EsVXOp4g7HoFOfVh9Lf7BmVVSRmg==",
"dependencies": {
"@types/bn.js": "^5.1.1",
- "web3-core": "1.10.0",
- "web3-core-helpers": "1.10.0",
- "web3-core-method": "1.10.0",
- "web3-core-promievent": "1.10.0",
- "web3-core-subscriptions": "1.10.0",
- "web3-eth-abi": "1.10.0",
- "web3-utils": "1.10.0"
+ "web3-core": "1.10.3",
+ "web3-core-helpers": "1.10.3",
+ "web3-core-method": "1.10.3",
+ "web3-core-promievent": "1.10.3",
+ "web3-core-subscriptions": "1.10.3",
+ "web3-eth-abi": "1.10.3",
+ "web3-utils": "1.10.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/web3-eth-contract/node_modules/@types/bn.js": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz",
- "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==",
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.3.tgz",
+ "integrity": "sha512-wT1B4iIO82ecXkdN6waCK8Ou7E71WU+mP1osDA5Q8c6Ur+ozU2vIKUIhSpUr6uE5L2YHocKS1Z2jG2fBC1YVeg==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/web3-eth-ens": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.10.0.tgz",
- "integrity": "sha512-3hpGgzX3qjgxNAmqdrC2YUQMTfnZbs4GeLEmy8aCWziVwogbuqQZ+Gzdfrym45eOZodk+lmXyLuAdqkNlvkc1g==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.10.3.tgz",
+ "integrity": "sha512-hR+odRDXGqKemw1GFniKBEXpjYwLgttTES+bc7BfTeoUyUZXbyDHe5ifC+h+vpzxh4oS0TnfcIoarK0Z9tFSiQ==",
"dependencies": {
"content-hash": "^2.5.2",
"eth-ens-namehash": "2.0.8",
- "web3-core": "1.10.0",
- "web3-core-helpers": "1.10.0",
- "web3-core-promievent": "1.10.0",
- "web3-eth-abi": "1.10.0",
- "web3-eth-contract": "1.10.0",
- "web3-utils": "1.10.0"
+ "web3-core": "1.10.3",
+ "web3-core-helpers": "1.10.3",
+ "web3-core-promievent": "1.10.3",
+ "web3-eth-abi": "1.10.3",
+ "web3-eth-contract": "1.10.3",
+ "web3-utils": "1.10.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/web3-eth-iban": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.10.0.tgz",
- "integrity": "sha512-0l+SP3IGhInw7Q20LY3IVafYEuufo4Dn75jAHT7c2aDJsIolvf2Lc6ugHkBajlwUneGfbRQs/ccYPQ9JeMUbrg==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.10.3.tgz",
+ "integrity": "sha512-ZCfOjYKAjaX2TGI8uif5ah+J3BYFuo+47JOIV1RIz2l7kD9VfnxvRH5UiQDRyMALQC7KFd2hUqIEtHklapNyKA==",
"dependencies": {
"bn.js": "^5.2.1",
- "web3-utils": "1.10.0"
+ "web3-utils": "1.10.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/web3-eth-personal": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.10.0.tgz",
- "integrity": "sha512-anseKn98w/d703eWq52uNuZi7GhQeVjTC5/svrBWEKob0WZ5kPdo+EZoFN0sp5a5ubbrk/E0xSl1/M5yORMtpg==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.10.3.tgz",
+ "integrity": "sha512-avrQ6yWdADIvuNQcFZXmGLCEzulQa76hUOuVywN7O3cklB4nFc/Gp3yTvD3bOAaE7DhjLQfhUTCzXL7WMxVTsw==",
"dependencies": {
"@types/node": "^12.12.6",
- "web3-core": "1.10.0",
- "web3-core-helpers": "1.10.0",
- "web3-core-method": "1.10.0",
- "web3-net": "1.10.0",
- "web3-utils": "1.10.0"
+ "web3-core": "1.10.3",
+ "web3-core-helpers": "1.10.3",
+ "web3-core-method": "1.10.3",
+ "web3-net": "1.10.3",
+ "web3-utils": "1.10.3"
},
"engines": {
"node": ">=8.0.0"
@@ -16632,13 +17138,13 @@
"integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="
},
"node_modules/web3-net": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.10.0.tgz",
- "integrity": "sha512-NLH/N3IshYWASpxk4/18Ge6n60GEvWBVeM8inx2dmZJVmRI6SJIlUxbL8jySgiTn3MMZlhbdvrGo8fpUW7a1GA==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.10.3.tgz",
+ "integrity": "sha512-IoSr33235qVoI1vtKssPUigJU9Fc/Ph0T9CgRi15sx+itysmvtlmXMNoyd6Xrgm9LuM4CIhxz7yDzH93B79IFg==",
"dependencies": {
- "web3-core": "1.10.0",
- "web3-core-method": "1.10.0",
- "web3-utils": "1.10.0"
+ "web3-core": "1.10.3",
+ "web3-core-method": "1.10.3",
+ "web3-utils": "1.10.3"
},
"engines": {
"node": ">=8.0.0"
@@ -16723,46 +17229,46 @@
}
},
"node_modules/web3-providers-http": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.10.0.tgz",
- "integrity": "sha512-eNr965YB8a9mLiNrkjAWNAPXgmQWfpBfkkn7tpEFlghfww0u3I0tktMZiaToJVcL2+Xq+81cxbkpeWJ5XQDwOA==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.10.3.tgz",
+ "integrity": "sha512-6dAgsHR3MxJ0Qyu3QLFlQEelTapVfWNTu5F45FYh8t7Y03T1/o+YAkVxsbY5AdmD+y5bXG/XPJ4q8tjL6MgZHw==",
"dependencies": {
- "abortcontroller-polyfill": "^1.7.3",
- "cross-fetch": "^3.1.4",
+ "abortcontroller-polyfill": "^1.7.5",
+ "cross-fetch": "^4.0.0",
"es6-promise": "^4.2.8",
- "web3-core-helpers": "1.10.0"
+ "web3-core-helpers": "1.10.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/web3-providers-http/node_modules/cross-fetch": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
- "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
+ "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
"dependencies": {
- "node-fetch": "2.6.7"
+ "node-fetch": "^2.6.12"
}
},
"node_modules/web3-providers-ipc": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.10.0.tgz",
- "integrity": "sha512-OfXG1aWN8L1OUqppshzq8YISkWrYHaATW9H8eh0p89TlWMc1KZOL9vttBuaBEi96D/n0eYDn2trzt22bqHWfXA==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.10.3.tgz",
+ "integrity": "sha512-vP5WIGT8FLnGRfswTxNs9rMfS1vCbMezj/zHbBe/zB9GauBRTYVrUo2H/hVrhLg8Ut7AbsKZ+tCJ4mAwpKi2hA==",
"dependencies": {
"oboe": "2.1.5",
- "web3-core-helpers": "1.10.0"
+ "web3-core-helpers": "1.10.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/web3-providers-ws": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.10.0.tgz",
- "integrity": "sha512-sK0fNcglW36yD5xjnjtSGBnEtf59cbw4vZzJ+CmOWIKGIR96mP5l684g0WD0Eo+f4NQc2anWWXG74lRc9OVMCQ==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.10.3.tgz",
+ "integrity": "sha512-/filBXRl48INxsh6AuCcsy4v5ndnTZ/p6bl67kmO9aK1wffv7CT++DrtclDtVMeDGCgB3van+hEf9xTAVXur7Q==",
"dependencies": {
"eventemitter3": "4.0.4",
- "web3-core-helpers": "1.10.0",
+ "web3-core-helpers": "1.10.3",
"websocket": "^1.0.32"
},
"engines": {
@@ -16770,28 +17276,29 @@
}
},
"node_modules/web3-shh": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.10.0.tgz",
- "integrity": "sha512-uNUUuNsO2AjX41GJARV9zJibs11eq6HtOe6Wr0FtRUcj8SN6nHeYIzwstAvJ4fXA53gRqFMTxdntHEt9aXVjpg==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.10.3.tgz",
+ "integrity": "sha512-cAZ60CPvs9azdwMSQ/PSUdyV4PEtaW5edAZhu3rCXf6XxQRliBboic+AvwUvB6j3eswY50VGa5FygfVmJ1JVng==",
"hasInstallScript": true,
"dependencies": {
- "web3-core": "1.10.0",
- "web3-core-method": "1.10.0",
- "web3-core-subscriptions": "1.10.0",
- "web3-net": "1.10.0"
+ "web3-core": "1.10.3",
+ "web3-core-method": "1.10.3",
+ "web3-core-subscriptions": "1.10.3",
+ "web3-net": "1.10.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/web3-utils": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.0.tgz",
- "integrity": "sha512-kSaCM0uMcZTNUSmn5vMEhlo02RObGNRRCkdX0V9UTAU0+lrvn0HSaudyCo6CQzuXUsnuY2ERJGCGPfeWmv19Rg==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.3.tgz",
+ "integrity": "sha512-OqcUrEE16fDBbGoQtZXWdavsPzbGIDc5v3VrRTZ0XrIpefC/viZ1ZU9bGEemazyS0catk/3rkOOxpzTfY+XsyQ==",
"dependencies": {
+ "@ethereumjs/util": "^8.1.0",
"bn.js": "^5.2.1",
"ethereum-bloom-filters": "^1.0.6",
- "ethereumjs-util": "^7.1.0",
+ "ethereum-cryptography": "^2.1.2",
"ethjs-unit": "0.1.6",
"number-to-bn": "1.7.0",
"randombytes": "^2.1.0",
@@ -16801,6 +17308,17 @@
"node": ">=8.0.0"
}
},
+ "node_modules/web3-utils/node_modules/ethereum-cryptography": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz",
+ "integrity": "sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug==",
+ "dependencies": {
+ "@noble/curves": "1.1.0",
+ "@noble/hashes": "1.3.1",
+ "@scure/bip32": "1.3.1",
+ "@scure/bip39": "1.2.1"
+ }
+ },
"node_modules/web3modal": {
"version": "1.9.12",
"resolved": "https://registry.npmjs.org/web3modal/-/web3modal-1.9.12.tgz",
@@ -16824,9 +17342,9 @@
}
},
"node_modules/webpack": {
- "version": "5.83.1",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.83.1.tgz",
- "integrity": "sha512-TNsG9jDScbNuB+Lb/3+vYolPplCS3bbEaJf+Bj0Gw4DhP3ioAflBb1flcRt9zsWITyvOhM96wMQNRWlSX52DgA==",
+ "version": "5.89.0",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz",
+ "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==",
"dev": true,
"dependencies": {
"@types/eslint-scope": "^3.7.3",
@@ -16835,10 +17353,10 @@
"@webassemblyjs/wasm-edit": "^1.11.5",
"@webassemblyjs/wasm-parser": "^1.11.5",
"acorn": "^8.7.1",
- "acorn-import-assertions": "^1.7.6",
+ "acorn-import-assertions": "^1.9.0",
"browserslist": "^4.14.5",
"chrome-trace-event": "^1.0.2",
- "enhanced-resolve": "^5.14.0",
+ "enhanced-resolve": "^5.15.0",
"es-module-lexer": "^1.2.1",
"eslint-scope": "5.1.1",
"events": "^3.2.0",
@@ -16848,7 +17366,7 @@
"loader-runner": "^4.2.0",
"mime-types": "^2.1.27",
"neo-async": "^2.6.2",
- "schema-utils": "^3.1.2",
+ "schema-utils": "^3.2.0",
"tapable": "^2.1.1",
"terser-webpack-plugin": "^5.3.7",
"watchpack": "^2.4.0",
@@ -16871,15 +17389,15 @@
}
},
"node_modules/webpack-cli": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.1.tgz",
- "integrity": "sha512-OLJwVMoXnXYH2ncNGU8gxVpUtm3ybvdioiTvHgUyBuyMLKiVvWy+QObzBsMtp5pH7qQoEuWgeEUQ/sU3ZJFzAw==",
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz",
+ "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==",
"dev": true,
"dependencies": {
"@discoveryjs/json-ext": "^0.5.0",
- "@webpack-cli/configtest": "^2.1.0",
- "@webpack-cli/info": "^2.0.1",
- "@webpack-cli/serve": "^2.0.4",
+ "@webpack-cli/configtest": "^2.1.1",
+ "@webpack-cli/info": "^2.0.2",
+ "@webpack-cli/serve": "^2.0.5",
"colorette": "^2.0.14",
"commander": "^10.0.1",
"cross-spawn": "^7.0.3",
@@ -16937,19 +17455,10 @@
"node": ">=10.0.0"
}
},
- "node_modules/webpack/node_modules/acorn-import-assertions": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
- "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
- "dev": true,
- "peerDependencies": {
- "acorn": "^8"
- }
- },
"node_modules/webpack/node_modules/schema-utils": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz",
- "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+ "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.8",
@@ -17090,16 +17599,15 @@
"integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q=="
},
"node_modules/which-typed-array": {
- "version": "1.1.9",
- "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz",
- "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==",
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz",
+ "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==",
"dependencies": {
"available-typed-arrays": "^1.0.5",
- "call-bind": "^1.0.2",
+ "call-bind": "^1.0.4",
"for-each": "^0.3.3",
"gopd": "^1.0.1",
- "has-tostringtag": "^1.0.0",
- "is-typed-array": "^1.1.10"
+ "has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
@@ -17115,9 +17623,9 @@
"dev": true
},
"node_modules/word-wrap": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
+ "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
@@ -17316,9 +17824,9 @@
"dev": true
},
"node_modules/yargs": {
- "version": "17.7.1",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz",
- "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==",
+ "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",
@@ -17351,17 +17859,6 @@
"node": ">=10"
}
},
- "node_modules/yn": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
- "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
- "dev": true,
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -17376,17 +17873,22 @@
}
},
"dependencies": {
+ "@aashutoshrathi/word-wrap": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
+ "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
+ "dev": true
+ },
"@amplitude/analytics-browser": {
- "version": "1.10.3",
- "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-1.10.3.tgz",
- "integrity": "sha512-u9xnHVLMtoWawZssZDC1wYR7GjuXF9DWL09fCdxf5j0Gp9CtRurzt+kUx9KC8IzrBmNSYZ7jG8xhwUWCbIFodg==",
- "requires": {
- "@amplitude/analytics-client-common": "^0.7.0",
- "@amplitude/analytics-core": "^0.13.3",
- "@amplitude/analytics-types": "^0.20.0",
- "@amplitude/plugin-page-view-tracking-browser": "^0.8.0",
- "@amplitude/plugin-web-attribution-browser": "^0.7.0",
- "@amplitude/ua-parser-js": "^0.7.31",
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.8.tgz",
+ "integrity": "sha512-K+12aAVJPzAtWIi8Ok5Q5dvg7v7IF4G0cI8PW0COWo3uTyY103r45OcpgrpRVpVAr+41d1eiMo36jqOke89uPA==",
+ "requires": {
+ "@amplitude/analytics-client-common": "^2.0.10",
+ "@amplitude/analytics-core": "^2.1.3",
+ "@amplitude/analytics-types": "^2.3.1",
+ "@amplitude/plugin-page-view-tracking-browser": "^2.0.18",
+ "@amplitude/plugin-web-attribution-browser": "^2.0.18",
"tslib": "^2.4.1"
},
"dependencies": {
@@ -17398,91 +17900,84 @@
}
},
"@amplitude/analytics-client-common": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-0.7.0.tgz",
- "integrity": "sha512-fKBR5DSo6qTqFcCFnLopRdWv6Zieox3YnzyXG2Zg1hR9MDeX96GoMEwwT2VB5MPzGxE1wYW6kQPvU2C/xpn5ww==",
+ "version": "2.0.10",
+ "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.10.tgz",
+ "integrity": "sha512-IaERGgBN3dmCGFbFd7SFTpTBguJIQzE/uDK44KEnLj0qw9wdoTxpLhoXQpqe5WKWsr46eONL9ROCJybHs4Efnw==",
"requires": {
- "@amplitude/analytics-connector": "^1.4.5",
- "@amplitude/analytics-core": "^0.13.3",
- "@amplitude/analytics-types": "^0.20.0",
+ "@amplitude/analytics-connector": "^1.4.8",
+ "@amplitude/analytics-core": "^2.1.3",
+ "@amplitude/analytics-types": "^2.3.1",
"tslib": "^2.4.1"
},
"dependencies": {
"tslib": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
- "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
}
}
},
"@amplitude/analytics-connector": {
- "version": "1.4.7",
- "resolved": "https://registry.npmjs.org/@amplitude/analytics-connector/-/analytics-connector-1.4.7.tgz",
- "integrity": "sha512-XjkFmVnJuPcRAQyPDs3LR3uam0jjDzBsT8ZABlWoV3iypRY7d+gs3ijDn2c4UU5XIjsR9GqzHCbZG3w7T+t6tQ==",
- "requires": {
- "@amplitude/ua-parser-js": "^0.7.31"
- }
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@amplitude/analytics-connector/-/analytics-connector-1.5.0.tgz",
+ "integrity": "sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g=="
},
"@amplitude/analytics-core": {
- "version": "0.13.3",
- "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-0.13.3.tgz",
- "integrity": "sha512-lLLmUwi78iqGsCPgd15yH488K+3FPYQ19fCdA3OemoEK9959xCzMZe/KYafQKkgZmI4ZiZLOp3pjamKWc6mcdQ==",
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.3.tgz",
+ "integrity": "sha512-WHXf9g33t63jYy4a/1uOpq/zHPMfEj5N2HHgJrg7Eu7v4w3kOWtPSMPBAllzFWxC5Ay5HeR9n0hqlJG0yffQWg==",
"requires": {
- "@amplitude/analytics-types": "^0.20.0",
+ "@amplitude/analytics-types": "^2.3.1",
"tslib": "^2.4.1"
},
"dependencies": {
"tslib": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
- "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
}
}
},
"@amplitude/analytics-types": {
- "version": "0.20.0",
- "resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-0.20.0.tgz",
- "integrity": "sha512-OYoUrf7QUl5sL4mqcQJBu+0GuSar7ga5prJv0HDtPT4LGu7Adv8S3AWljGiEJuTS/YaOMzvTe2qyKZbwyiNrMw=="
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-2.3.1.tgz",
+ "integrity": "sha512-yojBG20qvph0rpCJKb4i/FJa+otqLINEwv//hfzvjnCOcPPyS0YscI8oiRBM0rG7kZIDgaL9a6jPwkqK4ACmcw=="
},
"@amplitude/plugin-page-view-tracking-browser": {
- "version": "0.8.0",
- "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-0.8.0.tgz",
- "integrity": "sha512-Bun7aGo7woxsnupDEY1YugLQyAXDRtFhQsXnJKKCIkyeeRCq4qsuaAz0wI8w1QV+3IhaAf0lBuSqSuBIhPFDLQ==",
+ "version": "2.0.18",
+ "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.18.tgz",
+ "integrity": "sha512-s7PkGOgrx6U06/emzM8k+KRGDuyP9Z2L4OyGdeQwJcURJjiZDVQsmKlTZ5/SeGvxHYgq/4QJYUmMSmzByiGTCA==",
"requires": {
- "@amplitude/analytics-client-common": "^0.7.0",
- "@amplitude/analytics-types": "^0.20.0",
+ "@amplitude/analytics-client-common": "^2.0.10",
+ "@amplitude/analytics-types": "^2.3.1",
"tslib": "^2.4.1"
},
"dependencies": {
"tslib": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
- "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
}
}
},
"@amplitude/plugin-web-attribution-browser": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-0.7.0.tgz",
- "integrity": "sha512-PKC6SHIVGXvxhxO4CZQOYidoo+aKApLRFGBbRfDEVdSxEDY1b1wTHtqTJomxodqvw4pLocSDiuwC1s7B0p4zAQ==",
+ "version": "2.0.18",
+ "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.18.tgz",
+ "integrity": "sha512-mPlXu0fEYCCXhT6WpNJoM0FYkp+HIEm7L+KBEa2IHd3GD3+mh2AVDkZmgXLl3LKb++HY8mCiqC5/NcJ2AzTNhA==",
"requires": {
- "@amplitude/analytics-client-common": "^0.7.0",
- "@amplitude/analytics-types": "^0.20.0",
+ "@amplitude/analytics-client-common": "^2.0.10",
+ "@amplitude/analytics-core": "^2.1.3",
+ "@amplitude/analytics-types": "^2.3.1",
"tslib": "^2.4.1"
},
"dependencies": {
"tslib": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
- "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
}
}
},
- "@amplitude/ua-parser-js": {
- "version": "0.7.33",
- "resolved": "https://registry.npmjs.org/@amplitude/ua-parser-js/-/ua-parser-js-0.7.33.tgz",
- "integrity": "sha512-wKEtVR4vXuPT9cVEIJkYWnlF++Gx3BdLatPBM+SZ1ztVIvnhdGBZR/mn9x/PzyrMcRlZmyi6L56I2J3doVBnjA=="
- },
"@ampproject/remapping": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
@@ -17504,79 +17999,86 @@
}
},
"@babel/code-frame": {
- "version": "7.21.4",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz",
- "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==",
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
+ "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
"requires": {
- "@babel/highlight": "^7.18.6"
+ "@babel/highlight": "^7.23.4",
+ "chalk": "^2.4.2"
}
},
"@babel/compat-data": {
- "version": "7.21.7",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.7.tgz",
- "integrity": "sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA=="
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz",
+ "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw=="
},
"@babel/core": {
- "version": "7.21.8",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.8.tgz",
- "integrity": "sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==",
+ "version": "7.23.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz",
+ "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==",
"requires": {
"@ampproject/remapping": "^2.2.0",
- "@babel/code-frame": "^7.21.4",
- "@babel/generator": "^7.21.5",
- "@babel/helper-compilation-targets": "^7.21.5",
- "@babel/helper-module-transforms": "^7.21.5",
- "@babel/helpers": "^7.21.5",
- "@babel/parser": "^7.21.8",
- "@babel/template": "^7.20.7",
- "@babel/traverse": "^7.21.5",
- "@babel/types": "^7.21.5",
- "convert-source-map": "^1.7.0",
+ "@babel/code-frame": "^7.23.5",
+ "@babel/generator": "^7.23.6",
+ "@babel/helper-compilation-targets": "^7.23.6",
+ "@babel/helper-module-transforms": "^7.23.3",
+ "@babel/helpers": "^7.23.7",
+ "@babel/parser": "^7.23.6",
+ "@babel/template": "^7.22.15",
+ "@babel/traverse": "^7.23.7",
+ "@babel/types": "^7.23.6",
+ "convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
- "json5": "^2.2.2",
- "semver": "^6.3.0"
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "dependencies": {
+ "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=="
+ }
}
},
"@babel/generator": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.5.tgz",
- "integrity": "sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==",
+ "version": "7.23.6",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz",
+ "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==",
"requires": {
- "@babel/types": "^7.21.5",
+ "@babel/types": "^7.23.6",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
}
},
"@babel/helper-annotate-as-pure": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz",
- "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==",
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz",
+ "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==",
"requires": {
- "@babel/types": "^7.18.6"
+ "@babel/types": "^7.22.5"
}
},
"@babel/helper-builder-binary-assignment-operator-visitor": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz",
- "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz",
+ "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==",
"dev": true,
"requires": {
- "@babel/helper-explode-assignable-expression": "^7.18.6",
- "@babel/types": "^7.18.9"
+ "@babel/types": "^7.22.15"
}
},
"@babel/helper-compilation-targets": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz",
- "integrity": "sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==",
+ "version": "7.23.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz",
+ "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==",
"requires": {
- "@babel/compat-data": "^7.21.5",
- "@babel/helper-validator-option": "^7.21.0",
- "browserslist": "^4.21.3",
+ "@babel/compat-data": "^7.23.5",
+ "@babel/helper-validator-option": "^7.23.5",
+ "browserslist": "^4.22.2",
"lru-cache": "^5.1.1",
- "semver": "^6.3.0"
+ "semver": "^6.3.1"
},
"dependencies": {
"lru-cache": {
@@ -17595,29 +18097,31 @@
}
},
"@babel/helper-create-class-features-plugin": {
- "version": "7.21.4",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.4.tgz",
- "integrity": "sha512-46QrX2CQlaFRF4TkwfTt6nJD7IHq8539cCL7SDpqWSDeJKY1xylKKY5F/33mJhLZ3mFvKv2gGrVS6NkyF6qs+Q==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz",
+ "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==",
"dev": true,
"requires": {
- "@babel/helper-annotate-as-pure": "^7.18.6",
- "@babel/helper-environment-visitor": "^7.18.9",
- "@babel/helper-function-name": "^7.21.0",
- "@babel/helper-member-expression-to-functions": "^7.21.0",
- "@babel/helper-optimise-call-expression": "^7.18.6",
- "@babel/helper-replace-supers": "^7.20.7",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0",
- "@babel/helper-split-export-declaration": "^7.18.6"
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-function-name": "^7.22.5",
+ "@babel/helper-member-expression-to-functions": "^7.22.15",
+ "@babel/helper-optimise-call-expression": "^7.22.5",
+ "@babel/helper-replace-supers": "^7.22.9",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "semver": "^6.3.1"
}
},
"@babel/helper-create-regexp-features-plugin": {
- "version": "7.21.4",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.4.tgz",
- "integrity": "sha512-M00OuhU+0GyZ5iBBN9czjugzWrEq2vDpf/zCYHxxf93ul/Q5rv+a5h+/+0WnI1AebHNVtl5bFV0qsJoH23DbfA==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz",
+ "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==",
"dev": true,
"requires": {
- "@babel/helper-annotate-as-pure": "^7.18.6",
- "regexpu-core": "^5.3.1"
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "regexpu-core": "^5.3.1",
+ "semver": "^6.3.1"
}
},
"@babel/helper-define-polyfill-provider": {
@@ -17634,363 +18138,204 @@
}
},
"@babel/helper-environment-visitor": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz",
- "integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ=="
- },
- "@babel/helper-explode-assignable-expression": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz",
- "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==",
- "dev": true,
- "requires": {
- "@babel/types": "^7.18.6"
- }
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
+ "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA=="
},
"@babel/helper-function-name": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz",
- "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==",
+ "version": "7.23.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
+ "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
"requires": {
- "@babel/template": "^7.20.7",
- "@babel/types": "^7.21.0"
+ "@babel/template": "^7.22.15",
+ "@babel/types": "^7.23.0"
}
},
"@babel/helper-hoist-variables": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
- "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
+ "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
"requires": {
- "@babel/types": "^7.18.6"
+ "@babel/types": "^7.22.5"
}
},
"@babel/helper-member-expression-to-functions": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz",
- "integrity": "sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q==",
+ "version": "7.23.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz",
+ "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==",
"dev": true,
"requires": {
- "@babel/types": "^7.21.0"
+ "@babel/types": "^7.23.0"
}
},
"@babel/helper-module-imports": {
- "version": "7.21.4",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz",
- "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
+ "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
"requires": {
- "@babel/types": "^7.21.4"
+ "@babel/types": "^7.22.15"
}
},
"@babel/helper-module-transforms": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz",
- "integrity": "sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz",
+ "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==",
"requires": {
- "@babel/helper-environment-visitor": "^7.21.5",
- "@babel/helper-module-imports": "^7.21.4",
- "@babel/helper-simple-access": "^7.21.5",
- "@babel/helper-split-export-declaration": "^7.18.6",
- "@babel/helper-validator-identifier": "^7.19.1",
- "@babel/template": "^7.20.7",
- "@babel/traverse": "^7.21.5",
- "@babel/types": "^7.21.5"
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-module-imports": "^7.22.15",
+ "@babel/helper-simple-access": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "@babel/helper-validator-identifier": "^7.22.20"
}
},
"@babel/helper-optimise-call-expression": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz",
- "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==",
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz",
+ "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==",
"dev": true,
"requires": {
- "@babel/types": "^7.18.6"
+ "@babel/types": "^7.22.5"
}
},
"@babel/helper-plugin-utils": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz",
- "integrity": "sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg=="
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
},
"@babel/helper-remap-async-to-generator": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz",
- "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==",
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz",
+ "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==",
"dev": true,
"requires": {
- "@babel/helper-annotate-as-pure": "^7.18.6",
- "@babel/helper-environment-visitor": "^7.18.9",
- "@babel/helper-wrap-function": "^7.18.9",
- "@babel/types": "^7.18.9"
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-wrap-function": "^7.22.20"
}
},
"@babel/helper-replace-supers": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz",
- "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==",
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz",
+ "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==",
"dev": true,
"requires": {
- "@babel/helper-environment-visitor": "^7.18.9",
- "@babel/helper-member-expression-to-functions": "^7.20.7",
- "@babel/helper-optimise-call-expression": "^7.18.6",
- "@babel/template": "^7.20.7",
- "@babel/traverse": "^7.20.7",
- "@babel/types": "^7.20.7"
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-member-expression-to-functions": "^7.22.15",
+ "@babel/helper-optimise-call-expression": "^7.22.5"
}
},
"@babel/helper-simple-access": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz",
- "integrity": "sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==",
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
+ "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
"requires": {
- "@babel/types": "^7.21.5"
+ "@babel/types": "^7.22.5"
}
},
"@babel/helper-skip-transparent-expression-wrappers": {
- "version": "7.20.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz",
- "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==",
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz",
+ "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==",
"dev": true,
"requires": {
- "@babel/types": "^7.20.0"
+ "@babel/types": "^7.22.5"
}
},
"@babel/helper-split-export-declaration": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
- "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
+ "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
"requires": {
- "@babel/types": "^7.18.6"
+ "@babel/types": "^7.22.5"
}
},
"@babel/helper-string-parser": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz",
- "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w=="
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
+ "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ=="
},
"@babel/helper-validator-identifier": {
- "version": "7.19.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
- "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w=="
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
+ "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A=="
},
"@babel/helper-validator-option": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz",
- "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ=="
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz",
+ "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw=="
},
"@babel/helper-wrap-function": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz",
- "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==",
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz",
+ "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==",
"dev": true,
"requires": {
- "@babel/helper-function-name": "^7.19.0",
- "@babel/template": "^7.18.10",
- "@babel/traverse": "^7.20.5",
- "@babel/types": "^7.20.5"
+ "@babel/helper-function-name": "^7.22.5",
+ "@babel/template": "^7.22.15",
+ "@babel/types": "^7.22.19"
}
},
"@babel/helpers": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.5.tgz",
- "integrity": "sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==",
+ "version": "7.23.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.7.tgz",
+ "integrity": "sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ==",
"requires": {
- "@babel/template": "^7.20.7",
- "@babel/traverse": "^7.21.5",
- "@babel/types": "^7.21.5"
+ "@babel/template": "^7.22.15",
+ "@babel/traverse": "^7.23.7",
+ "@babel/types": "^7.23.6"
}
},
"@babel/highlight": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
- "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
+ "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
"requires": {
- "@babel/helper-validator-identifier": "^7.18.6",
- "chalk": "^2.0.0",
+ "@babel/helper-validator-identifier": "^7.22.20",
+ "chalk": "^2.4.2",
"js-tokens": "^4.0.0"
}
},
"@babel/parser": {
- "version": "7.21.8",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.8.tgz",
- "integrity": "sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA=="
+ "version": "7.23.6",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz",
+ "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ=="
},
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
- "version": "7.18.6",
- "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.18.6.tgz",
- "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==",
+ "version": "7.23.3",
+ "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.23.3.tgz",
+ "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz",
- "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0",
- "@babel/plugin-proposal-optional-chaining": "^7.20.7"
- }
- },
- "@babel/plugin-proposal-async-generator-functions": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz",
- "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==",
- "dev": true,
- "requires": {
- "@babel/helper-environment-visitor": "^7.18.9",
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/helper-remap-async-to-generator": "^7.18.9",
- "@babel/plugin-syntax-async-generators": "^7.8.4"
- }
- },
- "@babel/plugin-proposal-class-properties": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz",
- "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==",
- "dev": true,
- "requires": {
- "@babel/helper-create-class-features-plugin": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.18.6"
- }
- },
- "@babel/plugin-proposal-class-static-block": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz",
- "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==",
- "dev": true,
- "requires": {
- "@babel/helper-create-class-features-plugin": "^7.21.0",
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/plugin-syntax-class-static-block": "^7.14.5"
- }
- },
- "@babel/plugin-proposal-dynamic-import": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz",
- "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.18.6",
- "@babel/plugin-syntax-dynamic-import": "^7.8.3"
- }
- },
- "@babel/plugin-proposal-export-namespace-from": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz",
- "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.18.9",
- "@babel/plugin-syntax-export-namespace-from": "^7.8.3"
- }
- },
- "@babel/plugin-proposal-json-strings": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz",
- "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz",
+ "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.18.6",
- "@babel/plugin-syntax-json-strings": "^7.8.3"
- }
- },
- "@babel/plugin-proposal-logical-assignment-operators": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz",
- "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4"
- }
- },
- "@babel/plugin-proposal-nullish-coalescing-operator": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz",
- "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.18.6",
- "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3"
- }
- },
- "@babel/plugin-proposal-numeric-separator": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz",
- "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.18.6",
- "@babel/plugin-syntax-numeric-separator": "^7.10.4"
- }
- },
- "@babel/plugin-proposal-object-rest-spread": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz",
- "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==",
- "dev": true,
- "requires": {
- "@babel/compat-data": "^7.20.5",
- "@babel/helper-compilation-targets": "^7.20.7",
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
- "@babel/plugin-transform-parameters": "^7.20.7"
- }
- },
- "@babel/plugin-proposal-optional-catch-binding": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz",
- "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.18.6",
- "@babel/plugin-syntax-optional-catch-binding": "^7.8.3"
- }
- },
- "@babel/plugin-proposal-optional-chaining": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz",
- "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==",
- "dev": true,
- "requires": {
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0",
- "@babel/plugin-syntax-optional-chaining": "^7.8.3"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
+ "@babel/plugin-transform-optional-chaining": "^7.23.3"
}
},
- "@babel/plugin-proposal-private-methods": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz",
- "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==",
+ "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": {
+ "version": "7.23.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz",
+ "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==",
"dev": true,
"requires": {
- "@babel/helper-create-class-features-plugin": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
"@babel/plugin-proposal-private-property-in-object": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz",
- "integrity": "sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==",
- "dev": true,
- "requires": {
- "@babel/helper-annotate-as-pure": "^7.18.6",
- "@babel/helper-create-class-features-plugin": "^7.21.0",
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/plugin-syntax-private-property-in-object": "^7.14.5"
- }
- },
- "@babel/plugin-proposal-unicode-property-regex": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz",
- "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==",
+ "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,
- "requires": {
- "@babel/helper-create-regexp-features-plugin": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.18.6"
- }
+ "requires": {}
},
"@babel/plugin-syntax-async-generators": {
"version": "7.8.4",
@@ -18047,12 +18392,21 @@
}
},
"@babel/plugin-syntax-import-assertions": {
- "version": "7.20.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz",
- "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz",
+ "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-syntax-import-attributes": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz",
+ "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.19.0"
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
"@babel/plugin-syntax-import-meta": {
@@ -18074,12 +18428,12 @@
}
},
"@babel/plugin-syntax-jsx": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz",
- "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==",
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz",
+ "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
"@babel/plugin-syntax-logical-assignment-operators": {
@@ -18155,262 +18509,421 @@
}
},
"@babel/plugin-syntax-typescript": {
- "version": "7.20.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz",
- "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==",
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz",
+ "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@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,
"requires": {
- "@babel/helper-plugin-utils": "^7.19.0"
+ "@babel/helper-create-regexp-features-plugin": "^7.18.6",
+ "@babel/helper-plugin-utils": "^7.18.6"
}
},
"@babel/plugin-transform-arrow-functions": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz",
- "integrity": "sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz",
+ "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-async-generator-functions": {
+ "version": "7.23.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.7.tgz",
+ "integrity": "sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.21.5"
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-remap-async-to-generator": "^7.22.20",
+ "@babel/plugin-syntax-async-generators": "^7.8.4"
+ }
+ },
+ "@babel/plugin-transform-async-to-generator": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz",
+ "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-imports": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-remap-async-to-generator": "^7.22.20"
+ }
+ },
+ "@babel/plugin-transform-block-scoped-functions": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz",
+ "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-block-scoping": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz",
+ "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-class-properties": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz",
+ "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-class-static-block": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz",
+ "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5"
+ }
+ },
+ "@babel/plugin-transform-classes": {
+ "version": "7.23.8",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz",
+ "integrity": "sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@babel/helper-compilation-targets": "^7.23.6",
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-function-name": "^7.23.0",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-replace-supers": "^7.22.20",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "globals": "^11.1.0"
+ }
+ },
+ "@babel/plugin-transform-computed-properties": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz",
+ "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/template": "^7.22.15"
+ }
+ },
+ "@babel/plugin-transform-destructuring": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz",
+ "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-dotall-regex": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz",
+ "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-duplicate-keys": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz",
+ "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-dynamic-import": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz",
+ "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-exponentiation-operator": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz",
+ "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-export-namespace-from": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz",
+ "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-export-namespace-from": "^7.8.3"
}
},
- "@babel/plugin-transform-async-to-generator": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz",
- "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==",
+ "@babel/plugin-transform-for-of": {
+ "version": "7.23.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz",
+ "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==",
"dev": true,
"requires": {
- "@babel/helper-module-imports": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/helper-remap-async-to-generator": "^7.18.9"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5"
}
},
- "@babel/plugin-transform-block-scoped-functions": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz",
- "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==",
+ "@babel/plugin-transform-function-name": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz",
+ "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-compilation-targets": "^7.22.15",
+ "@babel/helper-function-name": "^7.23.0",
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
- "@babel/plugin-transform-block-scoping": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz",
- "integrity": "sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==",
+ "@babel/plugin-transform-json-strings": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz",
+ "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.20.2"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-json-strings": "^7.8.3"
}
},
- "@babel/plugin-transform-classes": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz",
- "integrity": "sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==",
- "dev": true,
- "requires": {
- "@babel/helper-annotate-as-pure": "^7.18.6",
- "@babel/helper-compilation-targets": "^7.20.7",
- "@babel/helper-environment-visitor": "^7.18.9",
- "@babel/helper-function-name": "^7.21.0",
- "@babel/helper-optimise-call-expression": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/helper-replace-supers": "^7.20.7",
- "@babel/helper-split-export-declaration": "^7.18.6",
- "globals": "^11.1.0"
+ "@babel/plugin-transform-literals": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz",
+ "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
- "@babel/plugin-transform-computed-properties": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.21.5.tgz",
- "integrity": "sha512-TR653Ki3pAwxBxUe8srfF3e4Pe3FTA46uaNHYyQwIoM4oWKSoOZiDNyHJ0oIoDIUPSRQbQG7jzgVBX3FPVne1Q==",
+ "@babel/plugin-transform-logical-assignment-operators": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz",
+ "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.21.5",
- "@babel/template": "^7.20.7"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4"
}
},
- "@babel/plugin-transform-destructuring": {
- "version": "7.21.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz",
- "integrity": "sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==",
+ "@babel/plugin-transform-member-expression-literals": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz",
+ "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.20.2"
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
- "@babel/plugin-transform-dotall-regex": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz",
- "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==",
+ "@babel/plugin-transform-modules-amd": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz",
+ "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==",
"dev": true,
"requires": {
- "@babel/helper-create-regexp-features-plugin": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-module-transforms": "^7.23.3",
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
- "@babel/plugin-transform-duplicate-keys": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz",
- "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==",
+ "@babel/plugin-transform-modules-commonjs": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz",
+ "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.18.9"
+ "@babel/helper-module-transforms": "^7.23.3",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-simple-access": "^7.22.5"
}
},
- "@babel/plugin-transform-exponentiation-operator": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz",
- "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==",
+ "@babel/plugin-transform-modules-systemjs": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz",
+ "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==",
"dev": true,
"requires": {
- "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-hoist-variables": "^7.22.5",
+ "@babel/helper-module-transforms": "^7.23.3",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.20"
}
},
- "@babel/plugin-transform-for-of": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz",
- "integrity": "sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ==",
+ "@babel/plugin-transform-modules-umd": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz",
+ "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.21.5"
+ "@babel/helper-module-transforms": "^7.23.3",
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
- "@babel/plugin-transform-function-name": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz",
- "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==",
+ "@babel/plugin-transform-named-capturing-groups-regex": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz",
+ "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==",
"dev": true,
"requires": {
- "@babel/helper-compilation-targets": "^7.18.9",
- "@babel/helper-function-name": "^7.18.9",
- "@babel/helper-plugin-utils": "^7.18.9"
+ "@babel/helper-create-regexp-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
- "@babel/plugin-transform-literals": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz",
- "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==",
+ "@babel/plugin-transform-new-target": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz",
+ "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.18.9"
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
- "@babel/plugin-transform-member-expression-literals": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz",
- "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==",
+ "@babel/plugin-transform-nullish-coalescing-operator": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz",
+ "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3"
}
},
- "@babel/plugin-transform-modules-amd": {
- "version": "7.20.11",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz",
- "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==",
+ "@babel/plugin-transform-numeric-separator": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz",
+ "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==",
"dev": true,
"requires": {
- "@babel/helper-module-transforms": "^7.20.11",
- "@babel/helper-plugin-utils": "^7.20.2"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4"
}
},
- "@babel/plugin-transform-modules-commonjs": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.5.tgz",
- "integrity": "sha512-OVryBEgKUbtqMoB7eG2rs6UFexJi6Zj6FDXx+esBLPTCxCNxAY9o+8Di7IsUGJ+AVhp5ncK0fxWUBd0/1gPhrQ==",
+ "@babel/plugin-transform-object-rest-spread": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz",
+ "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==",
"dev": true,
"requires": {
- "@babel/helper-module-transforms": "^7.21.5",
- "@babel/helper-plugin-utils": "^7.21.5",
- "@babel/helper-simple-access": "^7.21.5"
+ "@babel/compat-data": "^7.23.3",
+ "@babel/helper-compilation-targets": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-transform-parameters": "^7.23.3"
}
},
- "@babel/plugin-transform-modules-systemjs": {
- "version": "7.20.11",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz",
- "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==",
+ "@babel/plugin-transform-object-super": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz",
+ "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==",
"dev": true,
"requires": {
- "@babel/helper-hoist-variables": "^7.18.6",
- "@babel/helper-module-transforms": "^7.20.11",
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/helper-validator-identifier": "^7.19.1"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-replace-supers": "^7.22.20"
}
},
- "@babel/plugin-transform-modules-umd": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz",
- "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==",
+ "@babel/plugin-transform-optional-catch-binding": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz",
+ "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==",
"dev": true,
"requires": {
- "@babel/helper-module-transforms": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3"
}
},
- "@babel/plugin-transform-named-capturing-groups-regex": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz",
- "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==",
+ "@babel/plugin-transform-optional-chaining": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz",
+ "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==",
"dev": true,
"requires": {
- "@babel/helper-create-regexp-features-plugin": "^7.20.5",
- "@babel/helper-plugin-utils": "^7.20.2"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3"
}
},
- "@babel/plugin-transform-new-target": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz",
- "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==",
+ "@babel/plugin-transform-parameters": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz",
+ "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
- "@babel/plugin-transform-object-super": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz",
- "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==",
+ "@babel/plugin-transform-private-methods": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz",
+ "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.18.6",
- "@babel/helper-replace-supers": "^7.18.6"
+ "@babel/helper-create-class-features-plugin": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
- "@babel/plugin-transform-parameters": {
- "version": "7.21.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz",
- "integrity": "sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==",
+ "@babel/plugin-transform-private-property-in-object": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz",
+ "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.20.2"
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@babel/helper-create-class-features-plugin": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5"
}
},
"@babel/plugin-transform-property-literals": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz",
- "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz",
+ "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
"@babel/plugin-transform-regenerator": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz",
- "integrity": "sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz",
+ "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.21.5",
- "regenerator-transform": "^0.15.1"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "regenerator-transform": "^0.15.2"
}
},
"@babel/plugin-transform-reserved-words": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz",
- "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz",
+ "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
"@babel/plugin-transform-runtime": {
@@ -18438,103 +18951,111 @@
}
},
"@babel/plugin-transform-shorthand-properties": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz",
- "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz",
+ "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
"@babel/plugin-transform-spread": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz",
- "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz",
+ "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.20.2",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0"
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5"
}
},
"@babel/plugin-transform-sticky-regex": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz",
- "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz",
+ "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
"@babel/plugin-transform-template-literals": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz",
- "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz",
+ "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.18.9"
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
"@babel/plugin-transform-typeof-symbol": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz",
- "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz",
+ "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.18.9"
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
"@babel/plugin-transform-unicode-escapes": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.21.5.tgz",
- "integrity": "sha512-LYm/gTOwZqsYohlvFUe/8Tujz75LqqVC2w+2qPHLR+WyWHGCZPN1KBpJCJn+4Bk4gOkQy/IXKIge6az5MqwlOg==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz",
+ "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-unicode-property-regex": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz",
+ "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.21.5"
+ "@babel/helper-create-regexp-features-plugin": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
"@babel/plugin-transform-unicode-regex": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz",
- "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz",
+ "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==",
"dev": true,
"requires": {
- "@babel/helper-create-regexp-features-plugin": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.18.6"
+ "@babel/helper-create-regexp-features-plugin": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-unicode-sets-regex": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz",
+ "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.22.15",
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
"@babel/preset-env": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.21.5.tgz",
- "integrity": "sha512-wH00QnTTldTbf/IefEVyChtRdw5RJvODT/Vb4Vcxq1AZvtXj6T0YeX0cAcXhI6/BdGuiP3GcNIL4OQbI2DVNxg==",
- "dev": true,
- "requires": {
- "@babel/compat-data": "^7.21.5",
- "@babel/helper-compilation-targets": "^7.21.5",
- "@babel/helper-plugin-utils": "^7.21.5",
- "@babel/helper-validator-option": "^7.21.0",
- "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6",
- "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.20.7",
- "@babel/plugin-proposal-async-generator-functions": "^7.20.7",
- "@babel/plugin-proposal-class-properties": "^7.18.6",
- "@babel/plugin-proposal-class-static-block": "^7.21.0",
- "@babel/plugin-proposal-dynamic-import": "^7.18.6",
- "@babel/plugin-proposal-export-namespace-from": "^7.18.9",
- "@babel/plugin-proposal-json-strings": "^7.18.6",
- "@babel/plugin-proposal-logical-assignment-operators": "^7.20.7",
- "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
- "@babel/plugin-proposal-numeric-separator": "^7.18.6",
- "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
- "@babel/plugin-proposal-optional-catch-binding": "^7.18.6",
- "@babel/plugin-proposal-optional-chaining": "^7.21.0",
- "@babel/plugin-proposal-private-methods": "^7.18.6",
- "@babel/plugin-proposal-private-property-in-object": "^7.21.0",
- "@babel/plugin-proposal-unicode-property-regex": "^7.18.6",
+ "version": "7.23.8",
+ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.8.tgz",
+ "integrity": "sha512-lFlpmkApLkEP6woIKprO6DO60RImpatTQKtz4sUcDjVcK8M8mQ4sZsuxaTMNOZf0sqAq/ReYW1ZBHnOQwKpLWA==",
+ "dev": true,
+ "requires": {
+ "@babel/compat-data": "^7.23.5",
+ "@babel/helper-compilation-targets": "^7.23.6",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-validator-option": "^7.23.5",
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3",
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3",
+ "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.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.20.0",
+ "@babel/plugin-syntax-import-assertions": "^7.23.3",
+ "@babel/plugin-syntax-import-attributes": "^7.23.3",
"@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",
@@ -18545,67 +19066,105 @@
"@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-transform-arrow-functions": "^7.21.5",
- "@babel/plugin-transform-async-to-generator": "^7.20.7",
- "@babel/plugin-transform-block-scoped-functions": "^7.18.6",
- "@babel/plugin-transform-block-scoping": "^7.21.0",
- "@babel/plugin-transform-classes": "^7.21.0",
- "@babel/plugin-transform-computed-properties": "^7.21.5",
- "@babel/plugin-transform-destructuring": "^7.21.3",
- "@babel/plugin-transform-dotall-regex": "^7.18.6",
- "@babel/plugin-transform-duplicate-keys": "^7.18.9",
- "@babel/plugin-transform-exponentiation-operator": "^7.18.6",
- "@babel/plugin-transform-for-of": "^7.21.5",
- "@babel/plugin-transform-function-name": "^7.18.9",
- "@babel/plugin-transform-literals": "^7.18.9",
- "@babel/plugin-transform-member-expression-literals": "^7.18.6",
- "@babel/plugin-transform-modules-amd": "^7.20.11",
- "@babel/plugin-transform-modules-commonjs": "^7.21.5",
- "@babel/plugin-transform-modules-systemjs": "^7.20.11",
- "@babel/plugin-transform-modules-umd": "^7.18.6",
- "@babel/plugin-transform-named-capturing-groups-regex": "^7.20.5",
- "@babel/plugin-transform-new-target": "^7.18.6",
- "@babel/plugin-transform-object-super": "^7.18.6",
- "@babel/plugin-transform-parameters": "^7.21.3",
- "@babel/plugin-transform-property-literals": "^7.18.6",
- "@babel/plugin-transform-regenerator": "^7.21.5",
- "@babel/plugin-transform-reserved-words": "^7.18.6",
- "@babel/plugin-transform-shorthand-properties": "^7.18.6",
- "@babel/plugin-transform-spread": "^7.20.7",
- "@babel/plugin-transform-sticky-regex": "^7.18.6",
- "@babel/plugin-transform-template-literals": "^7.18.9",
- "@babel/plugin-transform-typeof-symbol": "^7.18.9",
- "@babel/plugin-transform-unicode-escapes": "^7.21.5",
- "@babel/plugin-transform-unicode-regex": "^7.18.6",
- "@babel/preset-modules": "^0.1.5",
- "@babel/types": "^7.21.5",
- "babel-plugin-polyfill-corejs2": "^0.3.3",
- "babel-plugin-polyfill-corejs3": "^0.6.0",
- "babel-plugin-polyfill-regenerator": "^0.4.1",
- "core-js-compat": "^3.25.1",
- "semver": "^6.3.0"
- },
- "dependencies": {
+ "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
+ "@babel/plugin-transform-arrow-functions": "^7.23.3",
+ "@babel/plugin-transform-async-generator-functions": "^7.23.7",
+ "@babel/plugin-transform-async-to-generator": "^7.23.3",
+ "@babel/plugin-transform-block-scoped-functions": "^7.23.3",
+ "@babel/plugin-transform-block-scoping": "^7.23.4",
+ "@babel/plugin-transform-class-properties": "^7.23.3",
+ "@babel/plugin-transform-class-static-block": "^7.23.4",
+ "@babel/plugin-transform-classes": "^7.23.8",
+ "@babel/plugin-transform-computed-properties": "^7.23.3",
+ "@babel/plugin-transform-destructuring": "^7.23.3",
+ "@babel/plugin-transform-dotall-regex": "^7.23.3",
+ "@babel/plugin-transform-duplicate-keys": "^7.23.3",
+ "@babel/plugin-transform-dynamic-import": "^7.23.4",
+ "@babel/plugin-transform-exponentiation-operator": "^7.23.3",
+ "@babel/plugin-transform-export-namespace-from": "^7.23.4",
+ "@babel/plugin-transform-for-of": "^7.23.6",
+ "@babel/plugin-transform-function-name": "^7.23.3",
+ "@babel/plugin-transform-json-strings": "^7.23.4",
+ "@babel/plugin-transform-literals": "^7.23.3",
+ "@babel/plugin-transform-logical-assignment-operators": "^7.23.4",
+ "@babel/plugin-transform-member-expression-literals": "^7.23.3",
+ "@babel/plugin-transform-modules-amd": "^7.23.3",
+ "@babel/plugin-transform-modules-commonjs": "^7.23.3",
+ "@babel/plugin-transform-modules-systemjs": "^7.23.3",
+ "@babel/plugin-transform-modules-umd": "^7.23.3",
+ "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5",
+ "@babel/plugin-transform-new-target": "^7.23.3",
+ "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4",
+ "@babel/plugin-transform-numeric-separator": "^7.23.4",
+ "@babel/plugin-transform-object-rest-spread": "^7.23.4",
+ "@babel/plugin-transform-object-super": "^7.23.3",
+ "@babel/plugin-transform-optional-catch-binding": "^7.23.4",
+ "@babel/plugin-transform-optional-chaining": "^7.23.4",
+ "@babel/plugin-transform-parameters": "^7.23.3",
+ "@babel/plugin-transform-private-methods": "^7.23.3",
+ "@babel/plugin-transform-private-property-in-object": "^7.23.4",
+ "@babel/plugin-transform-property-literals": "^7.23.3",
+ "@babel/plugin-transform-regenerator": "^7.23.3",
+ "@babel/plugin-transform-reserved-words": "^7.23.3",
+ "@babel/plugin-transform-shorthand-properties": "^7.23.3",
+ "@babel/plugin-transform-spread": "^7.23.3",
+ "@babel/plugin-transform-sticky-regex": "^7.23.3",
+ "@babel/plugin-transform-template-literals": "^7.23.3",
+ "@babel/plugin-transform-typeof-symbol": "^7.23.3",
+ "@babel/plugin-transform-unicode-escapes": "^7.23.3",
+ "@babel/plugin-transform-unicode-property-regex": "^7.23.3",
+ "@babel/plugin-transform-unicode-regex": "^7.23.3",
+ "@babel/plugin-transform-unicode-sets-regex": "^7.23.3",
+ "@babel/preset-modules": "0.1.6-no-external-plugins",
+ "babel-plugin-polyfill-corejs2": "^0.4.7",
+ "babel-plugin-polyfill-corejs3": "^0.8.7",
+ "babel-plugin-polyfill-regenerator": "^0.5.4",
+ "core-js-compat": "^3.31.0",
+ "semver": "^6.3.1"
+ },
+ "dependencies": {
+ "@babel/helper-define-polyfill-provider": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz",
+ "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==",
+ "dev": true,
+ "requires": {
+ "@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"
+ }
+ },
+ "babel-plugin-polyfill-corejs2": {
+ "version": "0.4.7",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz",
+ "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==",
+ "dev": true,
+ "requires": {
+ "@babel/compat-data": "^7.22.6",
+ "@babel/helper-define-polyfill-provider": "^0.4.4",
+ "semver": "^6.3.1"
+ }
+ },
"babel-plugin-polyfill-regenerator": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz",
- "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==",
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz",
+ "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==",
"dev": true,
"requires": {
- "@babel/helper-define-polyfill-provider": "^0.3.3"
+ "@babel/helper-define-polyfill-provider": "^0.4.4"
}
}
}
},
"@babel/preset-modules": {
- "version": "0.1.5",
- "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz",
- "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==",
+ "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,
"requires": {
"@babel/helper-plugin-utils": "^7.0.0",
- "@babel/plugin-proposal-unicode-property-regex": "^7.4.4",
- "@babel/plugin-transform-dotall-regex": "^7.4.4",
"@babel/types": "^7.4.4",
"esutils": "^2.0.2"
}
@@ -18625,39 +19184,39 @@
}
},
"@babel/template": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz",
- "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==",
+ "version": "7.22.15",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
+ "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
"requires": {
- "@babel/code-frame": "^7.18.6",
- "@babel/parser": "^7.20.7",
- "@babel/types": "^7.20.7"
+ "@babel/code-frame": "^7.22.13",
+ "@babel/parser": "^7.22.15",
+ "@babel/types": "^7.22.15"
}
},
"@babel/traverse": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz",
- "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==",
- "requires": {
- "@babel/code-frame": "^7.21.4",
- "@babel/generator": "^7.21.5",
- "@babel/helper-environment-visitor": "^7.21.5",
- "@babel/helper-function-name": "^7.21.0",
- "@babel/helper-hoist-variables": "^7.18.6",
- "@babel/helper-split-export-declaration": "^7.18.6",
- "@babel/parser": "^7.21.5",
- "@babel/types": "^7.21.5",
- "debug": "^4.1.0",
+ "version": "7.23.7",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz",
+ "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==",
+ "requires": {
+ "@babel/code-frame": "^7.23.5",
+ "@babel/generator": "^7.23.6",
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-function-name": "^7.23.0",
+ "@babel/helper-hoist-variables": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "@babel/parser": "^7.23.6",
+ "@babel/types": "^7.23.6",
+ "debug": "^4.3.1",
"globals": "^11.1.0"
}
},
"@babel/types": {
- "version": "7.21.5",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz",
- "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==",
+ "version": "7.23.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz",
+ "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==",
"requires": {
- "@babel/helper-string-parser": "^7.21.5",
- "@babel/helper-validator-identifier": "^7.19.1",
+ "@babel/helper-string-parser": "^7.23.4",
+ "@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
}
},
@@ -18667,31 +19226,6 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true
},
- "@cspotcode/source-map-support": {
- "version": "0.8.1",
- "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
- "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
- "dev": true,
- "optional": true,
- "peer": true,
- "requires": {
- "@jridgewell/trace-mapping": "0.3.9"
- },
- "dependencies": {
- "@jridgewell/trace-mapping": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
- "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
- "dev": true,
- "optional": true,
- "peer": true,
- "requires": {
- "@jridgewell/resolve-uri": "^3.0.3",
- "@jridgewell/sourcemap-codec": "^1.4.10"
- }
- }
- }
- },
"@discoveryjs/json-ext": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz",
@@ -18731,20 +19265,20 @@
}
},
"@eslint-community/regexpp": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.0.tgz",
- "integrity": "sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==",
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz",
+ "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==",
"dev": true
},
"@eslint/eslintrc": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz",
- "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==",
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
"dev": true,
"requires": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
- "espree": "^9.5.2",
+ "espree": "^9.6.0",
"globals": "^13.19.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
@@ -18760,9 +19294,9 @@
"dev": true
},
"globals": {
- "version": "13.20.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
- "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
+ "version": "13.23.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz",
+ "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==",
"dev": true,
"requires": {
"type-fest": "^0.20.2"
@@ -18786,27 +19320,55 @@
}
},
"@eslint/js": {
- "version": "8.41.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.41.0.tgz",
- "integrity": "sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz",
+ "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==",
"dev": true
},
"@ethereumjs/common": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.5.0.tgz",
- "integrity": "sha512-DEHjW6e38o+JmB/NO3GZBpW4lpaiBpkFgXF6jLcJ6gETBYpEyaA5nTimsWBUJR3Vmtm/didUEbNjajskugZORg==",
+ "version": "2.6.5",
+ "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.6.5.tgz",
+ "integrity": "sha512-lRyVQOeCDaIVtgfbowla32pzeDv2Obr8oR8Put5RdUBNRGr1VGPGQNGP6elWIpgK3YdpzqTOh4GyUGOureVeeA==",
"requires": {
"crc-32": "^1.2.0",
- "ethereumjs-util": "^7.1.1"
+ "ethereumjs-util": "^7.1.5"
}
},
+ "@ethereumjs/rlp": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-4.0.1.tgz",
+ "integrity": "sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw=="
+ },
"@ethereumjs/tx": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.3.2.tgz",
- "integrity": "sha512-6AaJhwg4ucmwTvw/1qLaZUX5miWrwZ4nLOUsKyb/HtzS3BMw/CasKhdi1ims9mBKeK9sOJCH4qGKOBGyJCeeog==",
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.5.2.tgz",
+ "integrity": "sha512-gQDNJWKrSDGu2w7w0PzVXVBNMzb7wwdDOmOqczmhNjqFxFuIbhVJDwiGEnxFNC2/b8ifcZzY7MLcluizohRzNw==",
+ "requires": {
+ "@ethereumjs/common": "^2.6.4",
+ "ethereumjs-util": "^7.1.5"
+ }
+ },
+ "@ethereumjs/util": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-8.1.0.tgz",
+ "integrity": "sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==",
"requires": {
- "@ethereumjs/common": "^2.5.0",
- "ethereumjs-util": "^7.1.2"
+ "@ethereumjs/rlp": "^4.0.1",
+ "ethereum-cryptography": "^2.0.0",
+ "micro-ftch": "^0.3.1"
+ },
+ "dependencies": {
+ "ethereum-cryptography": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz",
+ "integrity": "sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug==",
+ "requires": {
+ "@noble/curves": "1.1.0",
+ "@noble/hashes": "1.3.1",
+ "@scure/bip32": "1.3.1",
+ "@scure/bip39": "1.2.1"
+ }
+ }
}
},
"@ethersproject/abi": {
@@ -19004,17 +19566,17 @@
}
},
"@fortawesome/fontawesome-free": {
- "version": "6.4.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.0.tgz",
- "integrity": "sha512-0NyytTlPJwB/BF5LtRV8rrABDbe3TdTXqNB3PdZ+UUUZAEIrdOJdmABqKjt4AXwIoJNaRVVZEXxpNrqvE1GAYQ=="
+ "version": "6.5.1",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.1.tgz",
+ "integrity": "sha512-CNy5vSwN3fsUStPRLX7fUYojyuzoEMSXPl7zSLJ8TgtRfjv24LOnOWKT2zYwaHZCJGkdyRnTmstR0P+Ah503Gw=="
},
"@humanwhocodes/config-array": {
- "version": "0.11.8",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
- "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
+ "version": "0.11.13",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
+ "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
"dev": true,
"requires": {
- "@humanwhocodes/object-schema": "^1.2.1",
+ "@humanwhocodes/object-schema": "^2.0.1",
"debug": "^4.1.1",
"minimatch": "^3.0.5"
}
@@ -19026,9 +19588,9 @@
"dev": true
},
"@humanwhocodes/object-schema": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
- "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
+ "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
"dev": true
},
"@istanbuljs/load-nyc-config": {
@@ -19059,16 +19621,16 @@
"dev": true
},
"@jest/console": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz",
- "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz",
+ "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==",
"dev": true,
"requires": {
- "@jest/types": "^29.5.0",
+ "@jest/types": "^29.6.3",
"@types/node": "*",
"chalk": "^4.0.0",
- "jest-message-util": "^29.5.0",
- "jest-util": "^29.5.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
"slash": "^3.0.0"
},
"dependencies": {
@@ -19124,37 +19686,37 @@
}
},
"@jest/core": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz",
- "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz",
+ "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==",
"dev": true,
"requires": {
- "@jest/console": "^29.5.0",
- "@jest/reporters": "^29.5.0",
- "@jest/test-result": "^29.5.0",
- "@jest/transform": "^29.5.0",
- "@jest/types": "^29.5.0",
+ "@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.5.0",
- "jest-config": "^29.5.0",
- "jest-haste-map": "^29.5.0",
- "jest-message-util": "^29.5.0",
- "jest-regex-util": "^29.4.3",
- "jest-resolve": "^29.5.0",
- "jest-resolve-dependencies": "^29.5.0",
- "jest-runner": "^29.5.0",
- "jest-runtime": "^29.5.0",
- "jest-snapshot": "^29.5.0",
- "jest-util": "^29.5.0",
- "jest-validate": "^29.5.0",
- "jest-watcher": "^29.5.0",
+ "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.5.0",
+ "pretty-format": "^29.7.0",
"slash": "^3.0.0",
"strip-ansi": "^6.0.0"
},
@@ -19211,74 +19773,74 @@
}
},
"@jest/environment": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz",
- "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz",
+ "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==",
"dev": true,
"requires": {
- "@jest/fake-timers": "^29.5.0",
- "@jest/types": "^29.5.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
"@types/node": "*",
- "jest-mock": "^29.5.0"
+ "jest-mock": "^29.7.0"
}
},
"@jest/expect": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz",
- "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz",
+ "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==",
"dev": true,
"requires": {
- "expect": "^29.5.0",
- "jest-snapshot": "^29.5.0"
+ "expect": "^29.7.0",
+ "jest-snapshot": "^29.7.0"
}
},
"@jest/expect-utils": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz",
- "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz",
+ "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==",
"dev": true,
"requires": {
- "jest-get-type": "^29.4.3"
+ "jest-get-type": "^29.6.3"
}
},
"@jest/fake-timers": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz",
- "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==",
+ "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,
"requires": {
- "@jest/types": "^29.5.0",
+ "@jest/types": "^29.6.3",
"@sinonjs/fake-timers": "^10.0.2",
"@types/node": "*",
- "jest-message-util": "^29.5.0",
- "jest-mock": "^29.5.0",
- "jest-util": "^29.5.0"
+ "jest-message-util": "^29.7.0",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0"
}
},
"@jest/globals": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz",
- "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz",
+ "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==",
"dev": true,
"requires": {
- "@jest/environment": "^29.5.0",
- "@jest/expect": "^29.5.0",
- "@jest/types": "^29.5.0",
- "jest-mock": "^29.5.0"
+ "@jest/environment": "^29.7.0",
+ "@jest/expect": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "jest-mock": "^29.7.0"
}
},
"@jest/reporters": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz",
- "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz",
+ "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==",
"dev": true,
"requires": {
"@bcoe/v8-coverage": "^0.2.3",
- "@jest/console": "^29.5.0",
- "@jest/test-result": "^29.5.0",
- "@jest/transform": "^29.5.0",
- "@jest/types": "^29.5.0",
- "@jridgewell/trace-mapping": "^0.3.15",
+ "@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",
@@ -19286,13 +19848,13 @@
"glob": "^7.1.3",
"graceful-fs": "^4.2.9",
"istanbul-lib-coverage": "^3.0.0",
- "istanbul-lib-instrument": "^5.1.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.5.0",
- "jest-util": "^29.5.0",
- "jest-worker": "^29.5.0",
+ "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",
@@ -19340,13 +19902,13 @@
"dev": true
},
"jest-worker": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz",
- "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==",
+ "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,
"requires": {
"@types/node": "*",
- "jest-util": "^29.5.0",
+ "jest-util": "^29.7.0",
"merge-stream": "^2.0.0",
"supports-color": "^8.0.0"
},
@@ -19374,66 +19936,66 @@
}
},
"@jest/schemas": {
- "version": "29.4.3",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz",
- "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==",
+ "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,
"requires": {
- "@sinclair/typebox": "^0.25.16"
+ "@sinclair/typebox": "^0.27.8"
}
},
"@jest/source-map": {
- "version": "29.4.3",
- "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz",
- "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==",
+ "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,
"requires": {
- "@jridgewell/trace-mapping": "^0.3.15",
+ "@jridgewell/trace-mapping": "^0.3.18",
"callsites": "^3.0.0",
"graceful-fs": "^4.2.9"
}
},
"@jest/test-result": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz",
- "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz",
+ "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==",
"dev": true,
"requires": {
- "@jest/console": "^29.5.0",
- "@jest/types": "^29.5.0",
+ "@jest/console": "^29.7.0",
+ "@jest/types": "^29.6.3",
"@types/istanbul-lib-coverage": "^2.0.0",
"collect-v8-coverage": "^1.0.0"
}
},
"@jest/test-sequencer": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz",
- "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==",
+ "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,
"requires": {
- "@jest/test-result": "^29.5.0",
+ "@jest/test-result": "^29.7.0",
"graceful-fs": "^4.2.9",
- "jest-haste-map": "^29.5.0",
+ "jest-haste-map": "^29.7.0",
"slash": "^3.0.0"
}
},
"@jest/transform": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz",
- "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz",
+ "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==",
"dev": true,
"requires": {
"@babel/core": "^7.11.6",
- "@jest/types": "^29.5.0",
- "@jridgewell/trace-mapping": "^0.3.15",
+ "@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.5.0",
- "jest-regex-util": "^29.4.3",
- "jest-util": "^29.5.0",
+ "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",
@@ -19498,12 +20060,12 @@
}
},
"@jest/types": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz",
- "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==",
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+ "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
"dev": true,
"requires": {
- "@jest/schemas": "^29.4.3",
+ "@jest/schemas": "^29.6.3",
"@types/istanbul-lib-coverage": "^2.0.0",
"@types/istanbul-reports": "^3.0.0",
"@types/node": "*",
@@ -19598,9 +20160,9 @@
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
},
"@jridgewell/trace-mapping": {
- "version": "0.3.17",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
- "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
+ "version": "0.3.18",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
+ "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
"requires": {
"@jridgewell/resolve-uri": "3.1.0",
"@jridgewell/sourcemap-codec": "1.4.14"
@@ -19616,6 +20178,19 @@
"resolved": "https://registry.npmjs.org/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz",
"integrity": "sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q=="
},
+ "@noble/curves": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz",
+ "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==",
+ "requires": {
+ "@noble/hashes": "1.3.1"
+ }
+ },
+ "@noble/hashes": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
+ "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA=="
+ },
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -19642,10 +20217,34 @@
"fastq": "^1.6.0"
}
},
+ "@scure/base": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.3.tgz",
+ "integrity": "sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q=="
+ },
+ "@scure/bip32": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz",
+ "integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==",
+ "requires": {
+ "@noble/curves": "~1.1.0",
+ "@noble/hashes": "~1.3.1",
+ "@scure/base": "~1.1.0"
+ }
+ },
+ "@scure/bip39": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz",
+ "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==",
+ "requires": {
+ "@noble/hashes": "~1.3.0",
+ "@scure/base": "~1.1.0"
+ }
+ },
"@sinclair/typebox": {
- "version": "0.25.21",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.21.tgz",
- "integrity": "sha512-gFukHN4t8K4+wVC+ECqeqwzBDeFeTzBXroBTqE6vcWrQGbEUpHO7LYdG0f4xnvYq4VOEwITSlHlp0JBAIFMS/g==",
+ "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
},
"@sindresorhus/is": {
@@ -19653,6 +20252,12 @@
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
"integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="
},
+ "@sindresorhus/merge-streams": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz",
+ "integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==",
+ "dev": true
+ },
"@sinonjs/commons": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz",
@@ -19690,42 +20295,10 @@
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
"dev": true
},
- "@tsconfig/node10": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
- "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
- "dev": true,
- "optional": true,
- "peer": true
- },
- "@tsconfig/node12": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
- "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
- "dev": true,
- "optional": true,
- "peer": true
- },
- "@tsconfig/node14": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
- "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
- "dev": true,
- "optional": true,
- "peer": true
- },
- "@tsconfig/node16": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
- "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
- "dev": true,
- "optional": true,
- "peer": true
- },
"@types/babel__core": {
- "version": "7.20.0",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz",
- "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==",
+ "version": "7.20.1",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz",
+ "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==",
"dev": true,
"requires": {
"@babel/parser": "^7.20.7",
@@ -19755,12 +20328,12 @@
}
},
"@types/babel__traverse": {
- "version": "7.18.3",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz",
- "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==",
+ "version": "7.20.1",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz",
+ "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==",
"dev": true,
"requires": {
- "@babel/types": "^7.3.0"
+ "@babel/types": "^7.20.7"
}
},
"@types/bn.js": {
@@ -19818,9 +20391,9 @@
}
},
"@types/http-cache-semantics": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
- "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ=="
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz",
+ "integrity": "sha512-V46MYLFp08Wf2mmaBhvgjStM3tPa+2GAdy/iqoX+noX1//zje2x4XmrIU0cAwyClATsTmahbtoQ2EwP7I5WSiA=="
},
"@types/istanbul-lib-coverage": {
"version": "2.0.4",
@@ -19890,16 +20463,10 @@
"@types/node": "*"
}
},
- "@types/prettier": {
- "version": "2.7.2",
- "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz",
- "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==",
- "dev": true
- },
"@types/responselike": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
- "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.2.tgz",
+ "integrity": "sha512-/4YQT5Kp6HxUDb4yhRkm0bJ7TbjvTddqX7PZ5hz6qV3pxSo72f/6YPRo+Mu2DU307tm9IioO69l7uAwn5XNcFA==",
"requires": {
"@types/node": "*"
}
@@ -19939,6 +20506,12 @@
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==",
"dev": true
},
+ "@ungap/structured-clone": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
+ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
+ "dev": true
+ },
"@walletconnect/browser-utils": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@walletconnect/browser-utils/-/browser-utils-1.8.0.tgz",
@@ -20315,23 +20888,23 @@
}
},
"@webpack-cli/configtest": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.0.tgz",
- "integrity": "sha512-K/vuv72vpfSEZoo5KIU0a2FsEoYdW0DUMtMpB5X3LlUwshetMZRZRxB7sCsVji/lFaSxtQQ3aM9O4eMolXkU9w==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz",
+ "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==",
"dev": true,
"requires": {}
},
"@webpack-cli/info": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz",
- "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz",
+ "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==",
"dev": true,
"requires": {}
},
"@webpack-cli/serve": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.4.tgz",
- "integrity": "sha512-0xRgjgDLdz6G7+vvDLlaRpFatJaJ69uTalZLRSMX5B3VUrDmXcrVA3+6fXXQgmYz7bY9AAgs348XQdmtLsK41A==",
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz",
+ "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==",
"dev": true,
"requires": {}
},
@@ -20376,9 +20949,9 @@
}
},
"acorn": {
- "version": "8.8.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
- "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==",
+ "version": "8.9.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
+ "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
"dev": true
},
"acorn-globals": {
@@ -20399,6 +20972,13 @@
}
}
},
+ "acorn-import-assertions": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz",
+ "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
+ "dev": true,
+ "requires": {}
+ },
"acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
@@ -20506,14 +21086,6 @@
"picomatch": "^2.0.4"
}
},
- "arg": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
- "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
- "dev": true,
- "optional": true,
- "peer": true
- },
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -20523,48 +21095,86 @@
"sprintf-js": "~1.0.2"
}
},
+ "array-buffer-byte-length": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
+ "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "is-array-buffer": "^3.0.1"
+ }
+ },
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
},
"array-includes": {
- "version": "3.1.6",
- "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz",
- "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==",
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz",
+ "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
- "get-intrinsic": "^1.1.3",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "get-intrinsic": "^1.2.1",
"is-string": "^1.0.7"
}
},
+ "array.prototype.findlastindex": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz",
+ "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "es-shim-unscopables": "^1.0.0",
+ "get-intrinsic": "^1.2.1"
+ }
+ },
"array.prototype.flat": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz",
- "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz",
+ "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
"es-shim-unscopables": "^1.0.0"
}
},
"array.prototype.flatmap": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz",
- "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz",
+ "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
"es-shim-unscopables": "^1.0.0"
}
},
+ "arraybuffer.prototype.slice": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz",
+ "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==",
+ "dev": true,
+ "requires": {
+ "array-buffer-byte-length": "^1.0.0",
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "get-intrinsic": "^1.2.1",
+ "is-array-buffer": "^3.0.2",
+ "is-shared-array-buffer": "^1.0.2"
+ }
+ },
"asn1": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
@@ -20592,14 +21202,15 @@
}
},
"assert": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz",
- "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz",
+ "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==",
"requires": {
- "es6-object-assign": "^1.1.0",
- "is-nan": "^1.2.1",
- "object-is": "^1.0.1",
- "util": "^0.12.0"
+ "call-bind": "^1.0.2",
+ "is-nan": "^1.3.2",
+ "object-is": "^1.1.5",
+ "object.assign": "^4.1.4",
+ "util": "^0.12.5"
}
},
"assert-plus": {
@@ -20654,14 +21265,14 @@
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"autoprefixer": {
- "version": "10.4.14",
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz",
- "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==",
+ "version": "10.4.16",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz",
+ "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==",
"dev": true,
"requires": {
- "browserslist": "^4.21.5",
- "caniuse-lite": "^1.0.30001464",
- "fraction.js": "^4.2.0",
+ "browserslist": "^4.21.10",
+ "caniuse-lite": "^1.0.30001538",
+ "fraction.js": "^4.3.6",
"normalize-range": "^0.1.2",
"picocolors": "^1.0.0",
"postcss-value-parser": "^4.2.0"
@@ -20683,15 +21294,15 @@
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
},
"babel-jest": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz",
- "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==",
+ "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,
"requires": {
- "@jest/transform": "^29.5.0",
+ "@jest/transform": "^29.7.0",
"@types/babel__core": "^7.1.14",
"babel-plugin-istanbul": "^6.1.1",
- "babel-preset-jest": "^29.5.0",
+ "babel-preset-jest": "^29.6.3",
"chalk": "^4.0.0",
"graceful-fs": "^4.2.9",
"slash": "^3.0.0"
@@ -20749,12 +21360,12 @@
}
},
"babel-loader": {
- "version": "9.1.2",
- "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz",
- "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==",
+ "version": "9.1.3",
+ "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz",
+ "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==",
"dev": true,
"requires": {
- "find-cache-dir": "^3.3.2",
+ "find-cache-dir": "^4.0.0",
"schema-utils": "^4.0.0"
}
},
@@ -20769,12 +21380,27 @@
"@istanbuljs/schema": "^0.1.2",
"istanbul-lib-instrument": "^5.0.4",
"test-exclude": "^6.0.0"
+ },
+ "dependencies": {
+ "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,
+ "requires": {
+ "@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"
+ }
+ }
}
},
"babel-plugin-jest-hoist": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz",
- "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==",
+ "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,
"requires": {
"@babel/template": "^7.3.3",
@@ -20794,13 +21420,28 @@
}
},
"babel-plugin-polyfill-corejs3": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz",
- "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==",
+ "version": "0.8.7",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz",
+ "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==",
"dev": true,
"requires": {
- "@babel/helper-define-polyfill-provider": "^0.3.3",
- "core-js-compat": "^3.25.1"
+ "@babel/helper-define-polyfill-provider": "^0.4.4",
+ "core-js-compat": "^3.33.1"
+ },
+ "dependencies": {
+ "@babel/helper-define-polyfill-provider": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz",
+ "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==",
+ "dev": true,
+ "requires": {
+ "@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"
+ }
+ }
}
},
"babel-plugin-polyfill-regenerator": {
@@ -20848,12 +21489,12 @@
}
},
"babel-preset-jest": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz",
- "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==",
+ "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,
"requires": {
- "babel-plugin-jest-hoist": "^29.5.0",
+ "babel-plugin-jest-hoist": "^29.6.3",
"babel-preset-current-node-syntax": "^1.0.0"
}
},
@@ -20899,9 +21540,9 @@
"dev": true
},
"bignumber.js": {
- "version": "9.1.1",
- "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz",
- "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig=="
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
+ "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug=="
},
"binary-extensions": {
"version": "2.2.0",
@@ -21052,19 +21693,19 @@
}
},
"browserify-sign": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz",
- "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz",
+ "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==",
"requires": {
- "bn.js": "^5.1.1",
- "browserify-rsa": "^4.0.1",
+ "bn.js": "^5.2.1",
+ "browserify-rsa": "^4.1.0",
"create-hash": "^1.2.0",
"create-hmac": "^1.1.7",
- "elliptic": "^6.5.3",
+ "elliptic": "^6.5.4",
"inherits": "^2.0.4",
- "parse-asn1": "^5.1.5",
- "readable-stream": "^3.6.0",
- "safe-buffer": "^5.2.0"
+ "parse-asn1": "^5.1.6",
+ "readable-stream": "^3.6.2",
+ "safe-buffer": "^5.2.1"
},
"dependencies": {
"safe-buffer": {
@@ -21075,14 +21716,14 @@
}
},
"browserslist": {
- "version": "4.21.5",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
- "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==",
+ "version": "4.22.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz",
+ "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==",
"requires": {
- "caniuse-lite": "^1.0.30001449",
- "electron-to-chromium": "^1.4.284",
- "node-releases": "^2.0.8",
- "update-browserslist-db": "^1.0.10"
+ "caniuse-lite": "^1.0.30001565",
+ "electron-to-chromium": "^1.4.601",
+ "node-releases": "^2.0.14",
+ "update-browserslist-db": "^1.0.13"
}
},
"bs58": {
@@ -21206,9 +21847,9 @@
"integrity": "sha512-KJ/Dmo1lDDhmW2XDPMo+9oiy/CeqosPguPCrgcVzKyZrL6pM1gU2GmPY/xo6OQPTUaA/c0kwHuywB4E6nmT9ww=="
},
"cacheable-request": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz",
- "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==",
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz",
+ "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
"requires": {
"clone-response": "^1.0.2",
"get-stream": "^5.1.0",
@@ -21235,12 +21876,13 @@
}
},
"call-bind": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
- "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
+ "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==",
"requires": {
- "function-bind": "^1.1.1",
- "get-intrinsic": "^1.0.2"
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.1",
+ "set-function-length": "^1.1.1"
}
},
"callsites": {
@@ -21272,9 +21914,9 @@
}
},
"caniuse-lite": {
- "version": "1.0.30001464",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001464.tgz",
- "integrity": "sha512-oww27MtUmusatpRpCGSOneQk2/l5czXANDSFvsc7VuOQ86s3ANhZetpwXNf1zY/zdfP63Xvjz325DAdAoES13g=="
+ "version": "1.0.30001568",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001568.tgz",
+ "integrity": "sha512-vSUkH84HontZJ88MiNrOau1EBrCqEQYgkC5gIySiDlpsm8sGVrhU7Kx4V6h0tnqaHzIHZv08HlJIwPbL4XL9+A=="
},
"caseless": {
"version": "0.12.0",
@@ -21311,9 +21953,9 @@
"dev": true
},
"chart.js": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.0.tgz",
- "integrity": "sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==",
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz",
+ "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==",
"requires": {
"@kurkle/color": "^0.3.0"
}
@@ -21414,9 +22056,9 @@
}
},
"cjs-module-lexer": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz",
- "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==",
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz",
+ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==",
"dev": true
},
"class-is": {
@@ -21476,9 +22118,9 @@
"dev": true
},
"collect-v8-coverage": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz",
- "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==",
+ "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
},
"color-convert": {
@@ -21520,10 +22162,10 @@
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"dev": true
},
- "commondir": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
- "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+ "common-path-prefix": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz",
+ "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==",
"dev": true
},
"concat-map": {
@@ -21566,6 +22208,7 @@
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
"integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==",
+ "dev": true,
"requires": {
"safe-buffer": "~5.1.1"
}
@@ -21594,30 +22237,30 @@
}
},
"copy-webpack-plugin": {
- "version": "11.0.0",
- "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz",
- "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==",
+ "version": "12.0.1",
+ "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.1.tgz",
+ "integrity": "sha512-dhMfjJMYKDmmbG6Yn2pRSs1g8FgeQRtbE/JM6VAM9Xouk3KO1UVrwlLHLXxaI5F+o9WgnRfhFZzY9eV34O2gZQ==",
"dev": true,
"requires": {
- "fast-glob": "^3.2.11",
+ "fast-glob": "^3.3.2",
"glob-parent": "^6.0.1",
- "globby": "^13.1.1",
+ "globby": "^14.0.0",
"normalize-path": "^3.0.0",
- "schema-utils": "^4.0.0",
- "serialize-javascript": "^6.0.0"
+ "schema-utils": "^4.2.0",
+ "serialize-javascript": "^6.0.2"
}
},
"core-js": {
- "version": "3.30.2",
- "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.2.tgz",
- "integrity": "sha512-uBJiDmwqsbJCWHAwjrx3cvjbMXP7xD72Dmsn5LOJpiRmE3WbBbN5rCqQ2Qh6Ek6/eOrjlWngEynBWo4VxerQhg=="
+ "version": "3.35.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.0.tgz",
+ "integrity": "sha512-ntakECeqg81KqMueeGJ79Q5ZgQNR+6eaE8sxGCx62zMbAIj65q+uYvatToew3m6eAGdU4gNZwpZ34NMe4GYswg=="
},
"core-js-compat": {
- "version": "3.25.1",
- "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.25.1.tgz",
- "integrity": "sha512-pOHS7O0i8Qt4zlPW/eIFjwp+NrTPx+wTL0ctgI2fHn31sZOq89rDsmtc/A2vAX7r6shl+bmVI+678He46jgBlw==",
+ "version": "3.35.0",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.0.tgz",
+ "integrity": "sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==",
"requires": {
- "browserslist": "^4.21.3"
+ "browserslist": "^4.22.2"
}
},
"core-util-is": {
@@ -21635,14 +22278,14 @@
}
},
"cosmiconfig": {
- "version": "8.1.3",
- "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz",
- "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==",
+ "version": "8.3.6",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
+ "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
"dev": true,
"requires": {
- "import-fresh": "^3.2.1",
+ "import-fresh": "^3.3.0",
"js-yaml": "^4.1.0",
- "parse-json": "^5.0.0",
+ "parse-json": "^5.2.0",
"path-type": "^4.0.0"
},
"dependencies": {
@@ -21709,13 +22352,71 @@
"sha.js": "^2.4.8"
}
},
- "create-require": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
- "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "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,
- "optional": true,
- "peer": true
+ "requires": {
+ "@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"
+ },
+ "dependencies": {
+ "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,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "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,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "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
+ },
+ "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
+ },
+ "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,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
},
"cross-fetch": {
"version": "2.2.6",
@@ -21768,38 +22469,25 @@
"requires": {}
},
"css-loader": {
- "version": "5.2.7",
- "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz",
- "integrity": "sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==",
+ "version": "6.9.0",
+ "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.9.0.tgz",
+ "integrity": "sha512-3I5Nu4ytWlHvOP6zItjiHlefBNtrH+oehq8tnQa2kO305qpVyx9XNIT1CXIj5bgCJs7qICBCkgCYxQLKPANoLA==",
"dev": true,
"requires": {
"icss-utils": "^5.1.0",
- "loader-utils": "^2.0.0",
- "postcss": "^8.2.15",
+ "postcss": "^8.4.31",
"postcss-modules-extract-imports": "^3.0.0",
- "postcss-modules-local-by-default": "^4.0.0",
- "postcss-modules-scope": "^3.0.0",
+ "postcss-modules-local-by-default": "^4.0.3",
+ "postcss-modules-scope": "^3.1.0",
"postcss-modules-values": "^4.0.0",
- "postcss-value-parser": "^4.1.0",
- "schema-utils": "^3.0.0",
- "semver": "^7.3.5"
+ "postcss-value-parser": "^4.2.0",
+ "semver": "^7.5.4"
},
"dependencies": {
- "schema-utils": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
- "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
- "dev": true,
- "requires": {
- "@types/json-schema": "^7.0.8",
- "ajv": "^6.12.5",
- "ajv-keywords": "^3.5.2"
- }
- },
"semver": {
- "version": "7.3.5",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
- "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
@@ -21808,17 +22496,17 @@
}
},
"css-minimizer-webpack-plugin": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.0.tgz",
- "integrity": "sha512-1wZ/PYvg+ZKwi5FX6YrvbB31jMAdurS+CmRQLwWCVSlfzJC85l/a6RVICqUHFa+jXyhilfnCyjafzJGbmz5tcA==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz",
+ "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==",
"dev": true,
"requires": {
- "cssnano": "^6.0.0",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "cssnano": "^6.0.1",
"jest-worker": "^29.4.3",
- "postcss": "^8.4.21",
- "schema-utils": "^4.0.0",
- "serialize-javascript": "^6.0.1",
- "source-map": "^0.6.1"
+ "postcss": "^8.4.24",
+ "schema-utils": "^4.0.1",
+ "serialize-javascript": "^6.0.1"
},
"dependencies": {
"has-flag": {
@@ -21901,24 +22589,24 @@
"integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4="
},
"cssnano": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.0.tgz",
- "integrity": "sha512-RGlcbzGhzEBCHuQe3k+Udyj5M00z0pm9S+VurHXFEOXxH+y0sVrJH2sMzoyz2d8N1EScazg+DVvmgyx0lurwwA==",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.1.tgz",
+ "integrity": "sha512-fVO1JdJ0LSdIGJq68eIxOqFpIJrZqXUsBt8fkrBcztCQqAjQD51OhZp7tc0ImcbwXD4k7ny84QTV90nZhmqbkg==",
"dev": true,
"requires": {
- "cssnano-preset-default": "^6.0.0",
+ "cssnano-preset-default": "^6.0.1",
"lilconfig": "^2.1.0"
}
},
"cssnano-preset-default": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.0.tgz",
- "integrity": "sha512-BDxlaFzObRDXUiCCBQUNQcI+f1/aX2mgoNtXGjV6PG64POcHoDUoX+LgMWw+Q4609QhxwkcSnS65YFs42RA6qQ==",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.1.tgz",
+ "integrity": "sha512-7VzyFZ5zEB1+l1nToKyrRkuaJIx0zi/1npjvZfbBwbtNTzhLtlvYraK/7/uqmX2Wb2aQtd983uuGw79jAjLSuQ==",
"dev": true,
"requires": {
"css-declaration-sorter": "^6.3.1",
"cssnano-utils": "^4.0.0",
- "postcss-calc": "^8.2.3",
+ "postcss-calc": "^9.0.0",
"postcss-colormin": "^6.0.0",
"postcss-convert-values": "^6.0.0",
"postcss-discard-comments": "^6.0.0",
@@ -21926,7 +22614,7 @@
"postcss-discard-empty": "^6.0.0",
"postcss-discard-overridden": "^6.0.0",
"postcss-merge-longhand": "^6.0.0",
- "postcss-merge-rules": "^6.0.0",
+ "postcss-merge-rules": "^6.0.1",
"postcss-minify-font-values": "^6.0.0",
"postcss-minify-gradients": "^6.0.0",
"postcss-minify-params": "^6.0.0",
@@ -22072,10 +22760,11 @@
}
},
"dedent": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
- "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==",
- "dev": true
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz",
+ "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==",
+ "dev": true,
+ "requires": {}
},
"deep-eql": {
"version": "3.0.1",
@@ -22092,9 +22781,9 @@
"dev": true
},
"deepmerge": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz",
- "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==",
+ "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
},
"defer-to-connect": {
@@ -22110,10 +22799,20 @@
"abstract-leveldown": "~2.6.0"
}
},
+ "define-data-property": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
+ "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==",
+ "requires": {
+ "get-intrinsic": "^1.2.1",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.0"
+ }
+ },
"define-properties": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
- "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
+ "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
"requires": {
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
@@ -22159,18 +22858,10 @@
"integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
"dev": true
},
- "diff": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
- "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
- "dev": true,
- "optional": true,
- "peer": true
- },
"diff-sequences": {
- "version": "29.4.3",
- "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz",
- "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==",
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
+ "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
"dev": true
},
"diffie-hellman": {
@@ -22195,15 +22886,6 @@
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.2.tgz",
"integrity": "sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg=="
},
- "dir-glob": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
- "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
- "dev": true,
- "requires": {
- "path-type": "^4.0.0"
- }
- },
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -22254,14 +22936,14 @@
}
},
"domutils": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
- "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
+ "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
"dev": true,
"requires": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
- "domhandler": "^5.0.1"
+ "domhandler": "^5.0.3"
}
},
"dropzone": {
@@ -22284,9 +22966,9 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"electron-to-chromium": {
- "version": "1.4.327",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.327.tgz",
- "integrity": "sha512-DIk2H4g/3ZhjgiABJjVdQvUdMlSABOsjeCm6gmUzIdKxAuFrGiJ8QXMm3i09grZdDBMC/d8MELMrdwYRC0+YHg=="
+ "version": "1.4.609",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.609.tgz",
+ "integrity": "sha512-ihiCP7PJmjoGNuLpl7TjNA8pCQWu09vGyjlPYw1Rqww4gvNuCcmvl+44G+2QyJ6S2K4o+wbTS++Xz0YN8Q9ERw=="
},
"elliptic": {
"version": "6.5.4",
@@ -22363,9 +23045,9 @@
}
},
"enhanced-resolve": {
- "version": "5.14.0",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.0.tgz",
- "integrity": "sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw==",
+ "version": "5.15.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
+ "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.4",
@@ -22402,44 +23084,50 @@
}
},
"es-abstract": {
- "version": "1.21.1",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz",
- "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==",
+ "version": "1.22.3",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz",
+ "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==",
"dev": true,
"requires": {
+ "array-buffer-byte-length": "^1.0.0",
+ "arraybuffer.prototype.slice": "^1.0.2",
"available-typed-arrays": "^1.0.5",
- "call-bind": "^1.0.2",
+ "call-bind": "^1.0.5",
"es-set-tostringtag": "^2.0.1",
"es-to-primitive": "^1.2.1",
- "function-bind": "^1.1.1",
- "function.prototype.name": "^1.1.5",
- "get-intrinsic": "^1.1.3",
+ "function.prototype.name": "^1.1.6",
+ "get-intrinsic": "^1.2.2",
"get-symbol-description": "^1.0.0",
"globalthis": "^1.0.3",
"gopd": "^1.0.1",
- "has": "^1.0.3",
"has-property-descriptors": "^1.0.0",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3",
- "internal-slot": "^1.0.4",
- "is-array-buffer": "^3.0.1",
+ "hasown": "^2.0.0",
+ "internal-slot": "^1.0.5",
+ "is-array-buffer": "^3.0.2",
"is-callable": "^1.2.7",
"is-negative-zero": "^2.0.2",
"is-regex": "^1.1.4",
"is-shared-array-buffer": "^1.0.2",
"is-string": "^1.0.7",
- "is-typed-array": "^1.1.10",
+ "is-typed-array": "^1.1.12",
"is-weakref": "^1.0.2",
- "object-inspect": "^1.12.2",
+ "object-inspect": "^1.13.1",
"object-keys": "^1.1.1",
"object.assign": "^4.1.4",
- "regexp.prototype.flags": "^1.4.3",
+ "regexp.prototype.flags": "^1.5.1",
+ "safe-array-concat": "^1.0.1",
"safe-regex-test": "^1.0.0",
- "string.prototype.trimend": "^1.0.6",
- "string.prototype.trimstart": "^1.0.6",
+ "string.prototype.trim": "^1.2.8",
+ "string.prototype.trimend": "^1.0.7",
+ "string.prototype.trimstart": "^1.0.7",
+ "typed-array-buffer": "^1.0.0",
+ "typed-array-byte-length": "^1.0.0",
+ "typed-array-byte-offset": "^1.0.0",
"typed-array-length": "^1.0.4",
"unbox-primitive": "^1.0.2",
- "which-typed-array": "^1.1.9"
+ "which-typed-array": "^1.1.13"
}
},
"es-module-lexer": {
@@ -22449,23 +23137,23 @@
"dev": true
},
"es-set-tostringtag": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
- "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz",
+ "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==",
"dev": true,
"requires": {
- "get-intrinsic": "^1.1.3",
- "has": "^1.0.3",
- "has-tostringtag": "^1.0.0"
+ "get-intrinsic": "^1.2.2",
+ "has-tostringtag": "^1.0.0",
+ "hasown": "^2.0.0"
}
},
"es-shim-unscopables": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
- "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz",
+ "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==",
"dev": true,
"requires": {
- "has": "^1.0.3"
+ "hasown": "^2.0.0"
}
},
"es-to-primitive": {
@@ -22499,11 +23187,6 @@
"es6-symbol": "^3.1.1"
}
},
- "es6-object-assign": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz",
- "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw="
- },
"es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
@@ -22594,27 +23277,28 @@
}
},
"eslint": {
- "version": "8.41.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz",
- "integrity": "sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
+ "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
"dev": true,
"requires": {
"@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.4.0",
- "@eslint/eslintrc": "^2.0.3",
- "@eslint/js": "8.41.0",
- "@humanwhocodes/config-array": "^0.11.8",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.56.0",
+ "@humanwhocodes/config-array": "^0.11.13",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
- "ajv": "^6.10.0",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"debug": "^4.3.2",
"doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^7.2.0",
- "eslint-visitor-keys": "^3.4.1",
- "espree": "^9.5.2",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
"esquery": "^1.4.2",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@@ -22624,7 +23308,6 @@
"globals": "^13.19.0",
"graphemer": "^1.4.0",
"ignore": "^5.2.0",
- "import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"is-path-inside": "^3.0.3",
@@ -22634,9 +23317,8 @@
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
- "optionator": "^0.9.1",
+ "optionator": "^0.9.3",
"strip-ansi": "^6.0.1",
- "strip-json-comments": "^3.1.0",
"text-table": "^0.2.0"
},
"dependencies": {
@@ -22687,9 +23369,9 @@
"dev": true
},
"eslint-scope": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz",
- "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==",
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
"dev": true,
"requires": {
"esrecurse": "^4.3.0",
@@ -22778,21 +23460,21 @@
}
},
"eslint-config-standard": {
- "version": "17.0.0",
- "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz",
- "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==",
+ "version": "17.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz",
+ "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==",
"dev": true,
"requires": {}
},
"eslint-import-resolver-node": {
- "version": "0.3.7",
- "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz",
- "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==",
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
+ "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
"dev": true,
"requires": {
"debug": "^3.2.7",
- "is-core-module": "^2.11.0",
- "resolve": "^1.22.1"
+ "is-core-module": "^2.13.0",
+ "resolve": "^1.22.4"
},
"dependencies": {
"debug": {
@@ -22807,9 +23489,9 @@
}
},
"eslint-module-utils": {
- "version": "2.7.4",
- "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz",
- "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==",
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz",
+ "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==",
"dev": true,
"requires": {
"debug": "^3.2.7"
@@ -22838,26 +23520,28 @@
}
},
"eslint-plugin-import": {
- "version": "2.27.5",
- "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz",
- "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==",
+ "version": "2.29.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz",
+ "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==",
"dev": true,
"requires": {
- "array-includes": "^3.1.6",
- "array.prototype.flat": "^1.3.1",
- "array.prototype.flatmap": "^1.3.1",
+ "array-includes": "^3.1.7",
+ "array.prototype.findlastindex": "^1.2.3",
+ "array.prototype.flat": "^1.3.2",
+ "array.prototype.flatmap": "^1.3.2",
"debug": "^3.2.7",
"doctrine": "^2.1.0",
- "eslint-import-resolver-node": "^0.3.7",
- "eslint-module-utils": "^2.7.4",
- "has": "^1.0.3",
- "is-core-module": "^2.11.0",
+ "eslint-import-resolver-node": "^0.3.9",
+ "eslint-module-utils": "^2.8.0",
+ "hasown": "^2.0.0",
+ "is-core-module": "^2.13.1",
"is-glob": "^4.0.3",
"minimatch": "^3.1.2",
- "object.values": "^1.1.6",
- "resolve": "^1.22.1",
- "semver": "^6.3.0",
- "tsconfig-paths": "^3.14.1"
+ "object.fromentries": "^2.0.7",
+ "object.groupby": "^1.0.1",
+ "object.values": "^1.1.7",
+ "semver": "^6.3.1",
+ "tsconfig-paths": "^3.15.0"
},
"dependencies": {
"debug": {
@@ -22987,18 +23671,18 @@
}
},
"eslint-visitor-keys": {
- "version": "3.4.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
- "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true
},
"espree": {
- "version": "9.5.2",
- "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz",
- "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==",
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
"dev": true,
"requires": {
- "acorn": "^8.8.0",
+ "acorn": "^8.9.0",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^3.4.1"
}
@@ -23445,9 +24129,9 @@
},
"dependencies": {
"@types/bn.js": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz",
- "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==",
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.3.tgz",
+ "integrity": "sha512-wT1B4iIO82ecXkdN6waCK8Ou7E71WU+mP1osDA5Q8c6Ur+ozU2vIKUIhSpUr6uE5L2YHocKS1Z2jG2fBC1YVeg==",
"requires": {
"@types/node": "*"
}
@@ -23598,16 +24282,16 @@
"dev": true
},
"expect": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz",
- "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
+ "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
"dev": true,
"requires": {
- "@jest/expect-utils": "^29.5.0",
- "jest-get-type": "^29.4.3",
- "jest-matcher-utils": "^29.5.0",
- "jest-message-util": "^29.5.0",
- "jest-util": "^29.5.0"
+ "@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"
}
},
"express": {
@@ -23745,9 +24429,9 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"fast-glob": {
- "version": "3.2.11",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
- "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+ "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
"dev": true,
"requires": {
"@nodelib/fs.stat": "^2.0.2",
@@ -23879,25 +24563,13 @@
}
},
"find-cache-dir": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
- "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz",
+ "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==",
"dev": true,
"requires": {
- "commondir": "^1.0.1",
- "make-dir": "^3.0.2",
- "pkg-dir": "^4.1.0"
- },
- "dependencies": {
- "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,
- "requires": {
- "find-up": "^4.0.0"
- }
- }
+ "common-path-prefix": "^3.0.0",
+ "pkg-dir": "^7.0.0"
}
},
"find-up": {
@@ -23968,9 +24640,9 @@
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
},
"fraction.js": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
- "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz",
+ "integrity": "sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==",
"dev": true
},
"fresh": {
@@ -24010,20 +24682,20 @@
"optional": true
},
"function-bind": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
- "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
},
"function.prototype.name": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
- "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz",
+ "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.3",
- "es-abstract": "^1.19.0",
- "functions-have-names": "^1.2.2"
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "functions-have-names": "^1.2.3"
}
},
"functional-red-black-tree": {
@@ -24048,18 +24720,19 @@
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"get-func-name": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
- "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE="
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
+ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ=="
},
"get-intrinsic": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
- "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
+ "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==",
"requires": {
- "function-bind": "^1.1.1",
- "has": "^1.0.3",
- "has-symbols": "^1.0.3"
+ "function-bind": "^1.1.2",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "hasown": "^2.0.0"
}
},
"get-package-type": {
@@ -24144,22 +24817,29 @@
}
},
"globby": {
- "version": "13.1.2",
- "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz",
- "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==",
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz",
+ "integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==",
"dev": true,
"requires": {
- "dir-glob": "^3.0.1",
- "fast-glob": "^3.2.11",
- "ignore": "^5.2.0",
- "merge2": "^1.4.1",
- "slash": "^4.0.0"
+ "@sindresorhus/merge-streams": "^1.0.0",
+ "fast-glob": "^3.3.2",
+ "ignore": "^5.2.4",
+ "path-type": "^5.0.0",
+ "slash": "^5.1.0",
+ "unicorn-magic": "^0.1.0"
},
"dependencies": {
+ "path-type": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz",
+ "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==",
+ "dev": true
+ },
"slash": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
- "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
+ "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
"dev": true
}
}
@@ -24225,14 +24905,6 @@
"har-schema": "^2.0.0"
}
},
- "has": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
- "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
- "requires": {
- "function-bind": "^1.1.1"
- }
- },
"has-bigints": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
@@ -24255,8 +24927,7 @@
"has-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
- "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
- "dev": true
+ "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg=="
},
"has-symbols": {
"version": "1.0.3",
@@ -24297,10 +24968,18 @@
"minimalistic-assert": "^1.0.1"
}
},
+ "hasown": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
+ "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
+ "requires": {
+ "function-bind": "^1.1.2"
+ }
+ },
"highlight.js": {
- "version": "11.8.0",
- "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.8.0.tgz",
- "integrity": "sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg=="
+ "version": "11.9.0",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz",
+ "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw=="
},
"hmac-drbg": {
"version": "1.0.1",
@@ -24438,9 +25117,9 @@
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
},
"ignore": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
- "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
+ "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==",
"dev": true
},
"immediate": {
@@ -24507,13 +25186,13 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"internal-slot": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz",
- "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==",
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz",
+ "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==",
"dev": true,
"requires": {
- "get-intrinsic": "^1.1.3",
- "has": "^1.0.3",
+ "get-intrinsic": "^1.2.2",
+ "hasown": "^2.0.0",
"side-channel": "^1.0.4"
}
},
@@ -24538,13 +25217,13 @@
}
},
"is-array-buffer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz",
- "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
+ "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
- "get-intrinsic": "^1.1.3",
+ "get-intrinsic": "^1.2.0",
"is-typed-array": "^1.1.10"
}
},
@@ -24588,11 +25267,11 @@
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="
},
"is-core-module": {
- "version": "2.11.0",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
- "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
+ "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
"requires": {
- "has": "^1.0.3"
+ "hasown": "^2.0.0"
}
},
"is-date-object": {
@@ -24749,15 +25428,11 @@
}
},
"is-typed-array": {
- "version": "1.1.10",
- "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz",
- "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==",
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz",
+ "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==",
"requires": {
- "available-typed-arrays": "^1.0.5",
- "call-bind": "^1.0.2",
- "for-each": "^0.3.3",
- "gopd": "^1.0.1",
- "has-tostringtag": "^1.0.0"
+ "which-typed-array": "^1.1.11"
}
},
"is-typedarray": {
@@ -24803,26 +25478,37 @@
"dev": true
},
"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==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz",
+ "integrity": "sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==",
"dev": true,
"requires": {
"@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"
+ "semver": "^7.5.4"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
+ }
}
},
"istanbul-lib-report": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
- "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==",
+ "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,
"requires": {
"istanbul-lib-coverage": "^3.0.0",
- "make-dir": "^3.0.0",
+ "make-dir": "^4.0.0",
"supports-color": "^7.1.0"
},
"dependencies": {
@@ -24855,9 +25541,9 @@
}
},
"istanbul-reports": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz",
- "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==",
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz",
+ "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==",
"dev": true,
"requires": {
"html-escaper": "^2.0.0",
@@ -24865,50 +25551,51 @@
}
},
"jest": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz",
- "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
+ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
"dev": true,
"requires": {
- "@jest/core": "^29.5.0",
- "@jest/types": "^29.5.0",
+ "@jest/core": "^29.7.0",
+ "@jest/types": "^29.6.3",
"import-local": "^3.0.2",
- "jest-cli": "^29.5.0"
+ "jest-cli": "^29.7.0"
}
},
"jest-changed-files": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz",
- "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==",
+ "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,
"requires": {
"execa": "^5.0.0",
+ "jest-util": "^29.7.0",
"p-limit": "^3.1.0"
}
},
"jest-circus": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz",
- "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==",
+ "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,
"requires": {
- "@jest/environment": "^29.5.0",
- "@jest/expect": "^29.5.0",
- "@jest/test-result": "^29.5.0",
- "@jest/types": "^29.5.0",
+ "@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": "^0.7.0",
+ "dedent": "^1.0.0",
"is-generator-fn": "^2.0.0",
- "jest-each": "^29.5.0",
- "jest-matcher-utils": "^29.5.0",
- "jest-message-util": "^29.5.0",
- "jest-runtime": "^29.5.0",
- "jest-snapshot": "^29.5.0",
- "jest-util": "^29.5.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.5.0",
+ "pretty-format": "^29.7.0",
"pure-rand": "^6.0.0",
"slash": "^3.0.0",
"stack-utils": "^2.0.3"
@@ -24966,22 +25653,21 @@
}
},
"jest-cli": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz",
- "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==",
+ "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,
"requires": {
- "@jest/core": "^29.5.0",
- "@jest/test-result": "^29.5.0",
- "@jest/types": "^29.5.0",
+ "@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",
- "graceful-fs": "^4.2.9",
"import-local": "^3.0.2",
- "jest-config": "^29.5.0",
- "jest-util": "^29.5.0",
- "jest-validate": "^29.5.0",
- "prompts": "^2.0.1",
+ "jest-config": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
"yargs": "^17.3.1"
},
"dependencies": {
@@ -25037,31 +25723,31 @@
}
},
"jest-config": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz",
- "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz",
+ "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==",
"dev": true,
"requires": {
"@babel/core": "^7.11.6",
- "@jest/test-sequencer": "^29.5.0",
- "@jest/types": "^29.5.0",
- "babel-jest": "^29.5.0",
+ "@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.5.0",
- "jest-environment-node": "^29.5.0",
- "jest-get-type": "^29.4.3",
- "jest-regex-util": "^29.4.3",
- "jest-resolve": "^29.5.0",
- "jest-runner": "^29.5.0",
- "jest-util": "^29.5.0",
- "jest-validate": "^29.5.0",
+ "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.5.0",
+ "pretty-format": "^29.7.0",
"slash": "^3.0.0",
"strip-json-comments": "^3.1.1"
},
@@ -25118,15 +25804,15 @@
}
},
"jest-diff": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz",
- "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
+ "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==",
"dev": true,
"requires": {
"chalk": "^4.0.0",
- "diff-sequences": "^29.4.3",
- "jest-get-type": "^29.4.3",
- "pretty-format": "^29.5.0"
+ "diff-sequences": "^29.6.3",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
},
"dependencies": {
"ansi-styles": {
@@ -25181,25 +25867,25 @@
}
},
"jest-docblock": {
- "version": "29.4.3",
- "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz",
- "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz",
+ "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==",
"dev": true,
"requires": {
"detect-newline": "^3.0.0"
}
},
"jest-each": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz",
- "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==",
+ "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,
"requires": {
- "@jest/types": "^29.5.0",
+ "@jest/types": "^29.6.3",
"chalk": "^4.0.0",
- "jest-get-type": "^29.4.3",
- "jest-util": "^29.5.0",
- "pretty-format": "^29.5.0"
+ "jest-get-type": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "pretty-format": "^29.7.0"
},
"dependencies": {
"ansi-styles": {
@@ -25254,57 +25940,57 @@
}
},
"jest-environment-jsdom": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.5.0.tgz",
- "integrity": "sha512-/KG8yEK4aN8ak56yFVdqFDzKNHgF4BAymCx2LbPNPsUshUlfAl0eX402Xm1pt+eoG9SLZEUVifqXtX8SK74KCw==",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz",
+ "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==",
"dev": true,
"requires": {
- "@jest/environment": "^29.5.0",
- "@jest/fake-timers": "^29.5.0",
- "@jest/types": "^29.5.0",
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
"@types/jsdom": "^20.0.0",
"@types/node": "*",
- "jest-mock": "^29.5.0",
- "jest-util": "^29.5.0",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0",
"jsdom": "^20.0.0"
}
},
"jest-environment-node": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz",
- "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==",
+ "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,
"requires": {
- "@jest/environment": "^29.5.0",
- "@jest/fake-timers": "^29.5.0",
- "@jest/types": "^29.5.0",
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
"@types/node": "*",
- "jest-mock": "^29.5.0",
- "jest-util": "^29.5.0"
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0"
}
},
"jest-get-type": {
- "version": "29.4.3",
- "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz",
- "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==",
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
+ "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
"dev": true
},
"jest-haste-map": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz",
- "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==",
+ "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,
"requires": {
- "@jest/types": "^29.5.0",
+ "@jest/types": "^29.6.3",
"@types/graceful-fs": "^4.1.3",
"@types/node": "*",
"anymatch": "^3.0.3",
"fb-watchman": "^2.0.0",
"fsevents": "^2.3.2",
"graceful-fs": "^4.2.9",
- "jest-regex-util": "^29.4.3",
- "jest-util": "^29.5.0",
- "jest-worker": "^29.5.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "jest-worker": "^29.7.0",
"micromatch": "^4.0.4",
"walker": "^1.0.8"
},
@@ -25316,13 +26002,13 @@
"dev": true
},
"jest-worker": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz",
- "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==",
+ "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,
"requires": {
"@types/node": "*",
- "jest-util": "^29.5.0",
+ "jest-util": "^29.7.0",
"merge-stream": "^2.0.0",
"supports-color": "^8.0.0"
}
@@ -25339,25 +26025,25 @@
}
},
"jest-leak-detector": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz",
- "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==",
+ "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,
"requires": {
- "jest-get-type": "^29.4.3",
- "pretty-format": "^29.5.0"
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
}
},
"jest-matcher-utils": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz",
- "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==",
+ "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,
"requires": {
"chalk": "^4.0.0",
- "jest-diff": "^29.5.0",
- "jest-get-type": "^29.4.3",
- "pretty-format": "^29.5.0"
+ "jest-diff": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
},
"dependencies": {
"ansi-styles": {
@@ -25412,18 +26098,18 @@
}
},
"jest-message-util": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz",
- "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==",
+ "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,
"requires": {
"@babel/code-frame": "^7.12.13",
- "@jest/types": "^29.5.0",
+ "@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.5.0",
+ "pretty-format": "^29.7.0",
"slash": "^3.0.0",
"stack-utils": "^2.0.3"
},
@@ -25480,14 +26166,14 @@
}
},
"jest-mock": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz",
- "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz",
+ "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==",
"dev": true,
"requires": {
- "@jest/types": "^29.5.0",
+ "@jest/types": "^29.6.3",
"@types/node": "*",
- "jest-util": "^29.5.0"
+ "jest-util": "^29.7.0"
}
},
"jest-pnp-resolver": {
@@ -25498,23 +26184,23 @@
"requires": {}
},
"jest-regex-util": {
- "version": "29.4.3",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz",
- "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==",
+ "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
},
"jest-resolve": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz",
- "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz",
+ "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==",
"dev": true,
"requires": {
"chalk": "^4.0.0",
"graceful-fs": "^4.2.9",
- "jest-haste-map": "^29.5.0",
+ "jest-haste-map": "^29.7.0",
"jest-pnp-resolver": "^1.2.2",
- "jest-util": "^29.5.0",
- "jest-validate": "^29.5.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
"resolve": "^1.20.0",
"resolve.exports": "^2.0.0",
"slash": "^3.0.0"
@@ -25572,40 +26258,40 @@
}
},
"jest-resolve-dependencies": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz",
- "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==",
+ "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,
"requires": {
- "jest-regex-util": "^29.4.3",
- "jest-snapshot": "^29.5.0"
+ "jest-regex-util": "^29.6.3",
+ "jest-snapshot": "^29.7.0"
}
},
"jest-runner": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz",
- "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz",
+ "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==",
"dev": true,
"requires": {
- "@jest/console": "^29.5.0",
- "@jest/environment": "^29.5.0",
- "@jest/test-result": "^29.5.0",
- "@jest/transform": "^29.5.0",
- "@jest/types": "^29.5.0",
+ "@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.4.3",
- "jest-environment-node": "^29.5.0",
- "jest-haste-map": "^29.5.0",
- "jest-leak-detector": "^29.5.0",
- "jest-message-util": "^29.5.0",
- "jest-resolve": "^29.5.0",
- "jest-runtime": "^29.5.0",
- "jest-util": "^29.5.0",
- "jest-watcher": "^29.5.0",
- "jest-worker": "^29.5.0",
+ "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"
},
@@ -25651,13 +26337,13 @@
"dev": true
},
"jest-worker": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz",
- "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==",
+ "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,
"requires": {
"@types/node": "*",
- "jest-util": "^29.5.0",
+ "jest-util": "^29.7.0",
"merge-stream": "^2.0.0",
"supports-color": "^8.0.0"
},
@@ -25695,31 +26381,31 @@
}
},
"jest-runtime": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz",
- "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==",
- "dev": true,
- "requires": {
- "@jest/environment": "^29.5.0",
- "@jest/fake-timers": "^29.5.0",
- "@jest/globals": "^29.5.0",
- "@jest/source-map": "^29.4.3",
- "@jest/test-result": "^29.5.0",
- "@jest/transform": "^29.5.0",
- "@jest/types": "^29.5.0",
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz",
+ "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==",
+ "dev": true,
+ "requires": {
+ "@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.5.0",
- "jest-message-util": "^29.5.0",
- "jest-mock": "^29.5.0",
- "jest-regex-util": "^29.4.3",
- "jest-resolve": "^29.5.0",
- "jest-snapshot": "^29.5.0",
- "jest-util": "^29.5.0",
+ "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"
},
@@ -25776,34 +26462,31 @@
}
},
"jest-snapshot": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz",
- "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==",
+ "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,
"requires": {
"@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/traverse": "^7.7.2",
"@babel/types": "^7.3.3",
- "@jest/expect-utils": "^29.5.0",
- "@jest/transform": "^29.5.0",
- "@jest/types": "^29.5.0",
- "@types/babel__traverse": "^7.0.6",
- "@types/prettier": "^2.1.5",
+ "@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.5.0",
+ "expect": "^29.7.0",
"graceful-fs": "^4.2.9",
- "jest-diff": "^29.5.0",
- "jest-get-type": "^29.4.3",
- "jest-matcher-utils": "^29.5.0",
- "jest-message-util": "^29.5.0",
- "jest-util": "^29.5.0",
+ "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.5.0",
- "semver": "^7.3.5"
+ "pretty-format": "^29.7.0",
+ "semver": "^7.5.3"
},
"dependencies": {
"ansi-styles": {
@@ -25847,9 +26530,9 @@
"dev": true
},
"semver": {
- "version": "7.3.8",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
- "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
@@ -25867,12 +26550,12 @@
}
},
"jest-util": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz",
- "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==",
+ "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,
"requires": {
- "@jest/types": "^29.5.0",
+ "@jest/types": "^29.6.3",
"@types/node": "*",
"chalk": "^4.0.0",
"ci-info": "^3.2.0",
@@ -25932,17 +26615,17 @@
}
},
"jest-validate": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz",
- "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==",
+ "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,
"requires": {
- "@jest/types": "^29.5.0",
+ "@jest/types": "^29.6.3",
"camelcase": "^6.2.0",
"chalk": "^4.0.0",
- "jest-get-type": "^29.4.3",
+ "jest-get-type": "^29.6.3",
"leven": "^3.1.0",
- "pretty-format": "^29.5.0"
+ "pretty-format": "^29.7.0"
},
"dependencies": {
"ansi-styles": {
@@ -26003,18 +26686,18 @@
}
},
"jest-watcher": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz",
- "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==",
+ "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,
"requires": {
- "@jest/test-result": "^29.5.0",
- "@jest/types": "^29.5.0",
+ "@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.5.0",
+ "jest-util": "^29.7.0",
"string-length": "^4.0.1"
},
"dependencies": {
@@ -26098,15 +26781,15 @@
}
},
"jiti": {
- "version": "1.18.2",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz",
- "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==",
+ "version": "1.21.0",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz",
+ "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==",
"dev": true
},
"jquery": {
- "version": "3.7.0",
- "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz",
- "integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ=="
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
+ "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
},
"jquery-mousewheel": {
"version": "3.1.13",
@@ -26317,9 +27000,9 @@
}
},
"keyv": {
- "version": "4.5.2",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz",
- "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==",
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"requires": {
"json-buffer": "3.0.1"
}
@@ -26341,12 +27024,6 @@
"integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
"dev": true
},
- "klona": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz",
- "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==",
- "dev": true
- },
"level-codec": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/level-codec/-/level-codec-7.0.1.tgz",
@@ -26649,27 +27326,30 @@
"integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU="
},
"luxon": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz",
- "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg=="
+ "version": "3.4.4",
+ "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz",
+ "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA=="
},
"make-dir": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
- "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "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,
"requires": {
- "semver": "^6.0.0"
+ "semver": "^7.5.3"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
+ }
}
},
- "make-error": {
- "version": "1.3.6",
- "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
- "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
- "dev": true,
- "optional": true,
- "peer": true
- },
"makeerror": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
@@ -26816,6 +27496,11 @@
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
},
+ "micro-ftch": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/micro-ftch/-/micro-ftch-0.3.1.tgz",
+ "integrity": "sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg=="
+ },
"micromatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
@@ -26880,9 +27565,9 @@
}
},
"mini-css-extract-plugin": {
- "version": "2.7.6",
- "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz",
- "integrity": "sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==",
+ "version": "2.7.7",
+ "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.7.tgz",
+ "integrity": "sha512-+0n11YGyRavUR3IlaOzJ0/4Il1avMvJ1VJfhWfCn24ITQXhRr1gghbhhrda6tgtNcpZaWKdSuwKq20Jb7fnlyw==",
"dev": true,
"requires": {
"schema-utils": "^4.0.0"
@@ -26937,9 +27622,9 @@
}
},
"mixpanel-browser": {
- "version": "2.47.0",
- "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.47.0.tgz",
- "integrity": "sha512-Ldrva0fRBEIFWmEibBQO1PulfpJVF3pf28Guk09lDirDaSQqqU/xs9zQLwN2rL5VwVtsP1aD3JaCgaa98EjojQ=="
+ "version": "2.48.1",
+ "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.48.1.tgz",
+ "integrity": "sha512-vXTuUzZMg+ht7sRqyjtc3dUDy/81Z/H6FLFgFkUZJqKFaAqcx1JSXmOdY/2kmsxCkUdy5JN5zW9m9TMCk+rxGQ=="
},
"mkdirp": {
"version": "3.0.1",
@@ -26960,9 +27645,9 @@
"integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw=="
},
"moment": {
- "version": "2.29.4",
- "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
- "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
+ "version": "2.30.1",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
+ "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="
},
"ms": {
"version": "2.1.2",
@@ -27018,9 +27703,9 @@
"integrity": "sha1-TzFS4JVA/eKMdvRLGbvNHVpCR40="
},
"nanoid": {
- "version": "3.3.6",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
- "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true
},
"nanomorph": {
@@ -27059,9 +27744,9 @@
"integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA=="
},
"node-fetch": {
- "version": "2.6.7",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
- "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"requires": {
"whatwg-url": "^5.0.0"
},
@@ -27099,9 +27784,9 @@
"dev": true
},
"node-releases": {
- "version": "2.0.10",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
- "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w=="
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
+ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw=="
},
"normalize-path": {
"version": "3.0.0",
@@ -27176,9 +27861,9 @@
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"object-inspect": {
- "version": "1.12.3",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
- "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g=="
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
+ "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ=="
},
"object-is": {
"version": "1.1.5",
@@ -27198,7 +27883,6 @@
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
"integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
- "dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.4",
@@ -27206,15 +27890,38 @@
"object-keys": "^1.1.1"
}
},
+ "object.fromentries": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz",
+ "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
+ }
+ },
+ "object.groupby": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz",
+ "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "get-intrinsic": "^1.2.1"
+ }
+ },
"object.values": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz",
- "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==",
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz",
+ "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
}
},
"oboe": {
@@ -27251,17 +27958,17 @@
}
},
"optionator": {
- "version": "0.9.1",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
- "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
+ "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
"dev": true,
"requires": {
+ "@aashutoshrathi/word-wrap": "^1.2.3",
"deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6",
"levn": "^0.4.1",
"prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.3"
+ "type-check": "^0.4.0"
}
},
"os-browserify": {
@@ -27431,9 +28138,9 @@
"version": "file:../../../deps/phoenix_html"
},
"photoswipe": {
- "version": "5.3.7",
- "resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.3.7.tgz",
- "integrity": "sha512-zsyLsTTLFrj0XR1m4/hO7qNooboFKUrDy+Zt5i2d6qjFPAtBjzaj/Xtydso4uxzcXpcqbTmyxDibb3BcSISseg=="
+ "version": "5.4.3",
+ "resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.4.3.tgz",
+ "integrity": "sha512-9UC6oJBK4oXFZ5HcdlcvGkfEHsVrmE4csUdCQhEjHYb3PvPLO3PG7UhnPuOgjxwmhq5s17Un5NUdum01LgBDng=="
},
"picocolors": {
"version": "1.0.0",
@@ -27457,11 +28164,71 @@
"integrity": "sha512-TNtsE+34BIax3WtkB/qqu5uepV1McKYEgvL3kWzU7aqPCpMEN6rBF3AOwu4WCwAealWlBGobXny/9kJb49C1ew=="
},
"pirates": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
- "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==",
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
+ "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
"dev": true
},
+ "pkg-dir": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz",
+ "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==",
+ "dev": true,
+ "requires": {
+ "find-up": "^6.3.0"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz",
+ "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^7.1.0",
+ "path-exists": "^5.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz",
+ "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^6.0.0"
+ }
+ },
+ "p-limit": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
+ "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
+ "dev": true,
+ "requires": {
+ "yocto-queue": "^1.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz",
+ "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^4.0.0"
+ }
+ },
+ "path-exists": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz",
+ "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==",
+ "dev": true
+ },
+ "yocto-queue": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
+ "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
+ "dev": true
+ }
+ }
+ },
"pngjs": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
@@ -27473,23 +28240,23 @@
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
},
"postcss": {
- "version": "8.4.23",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz",
- "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==",
+ "version": "8.4.33",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
+ "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
"dev": true,
"requires": {
- "nanoid": "^3.3.6",
+ "nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
},
"postcss-calc": {
- "version": "8.2.4",
- "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz",
- "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==",
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz",
+ "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==",
"dev": true,
"requires": {
- "postcss-selector-parser": "^6.0.9",
+ "postcss-selector-parser": "^6.0.11",
"postcss-value-parser": "^4.2.0"
}
},
@@ -27544,21 +28311,20 @@
"requires": {}
},
"postcss-loader": {
- "version": "7.3.0",
- "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.0.tgz",
- "integrity": "sha512-qLAFjvR2BFNz1H930P7mj1iuWJFjGey/nVhimfOAAQ1ZyPpcClAxP8+A55Sl8mBvM+K2a9Pjgdj10KpANWrNfw==",
+ "version": "7.3.4",
+ "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz",
+ "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==",
"dev": true,
"requires": {
- "cosmiconfig": "^8.1.3",
- "jiti": "^1.18.2",
- "klona": "^2.0.6",
- "semver": "^7.3.8"
+ "cosmiconfig": "^8.3.5",
+ "jiti": "^1.20.0",
+ "semver": "^7.5.4"
},
"dependencies": {
"semver": {
- "version": "7.3.8",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
- "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
@@ -27577,9 +28343,9 @@
}
},
"postcss-merge-rules": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.0.tgz",
- "integrity": "sha512-rCXkklftzEkniyv3f4mRCQzxD6oE4Quyh61uyWTUbCJ26Pv2hoz+fivJSsSBWxDBeScR4fKCfF3HHTcD7Ybqnw==",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.1.tgz",
+ "integrity": "sha512-a4tlmJIQo9SCjcfiCcCMg/ZCEe0XTkl/xK0XHBs955GWg9xDX3NwP9pwZ78QUOWB8/0XCjZeJn98Dae0zg6AAw==",
"dev": true,
"requires": {
"browserslist": "^4.21.4",
@@ -27636,9 +28402,9 @@
"requires": {}
},
"postcss-modules-local-by-default": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz",
- "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz",
+ "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==",
"dev": true,
"requires": {
"icss-utils": "^5.0.0",
@@ -27647,9 +28413,9 @@
}
},
"postcss-modules-scope": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz",
- "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.0.tgz",
+ "integrity": "sha512-SaIbK8XW+MZbd0xHPf7kdfA/3eOt7vxJ72IRecn3EzuZVLr1r0orzf0MX/pN8m+NMDoo6X/SQd8oeKqGZd8PXg==",
"dev": true,
"requires": {
"postcss-selector-parser": "^6.0.4"
@@ -27774,9 +28540,9 @@
}
},
"postcss-selector-parser": {
- "version": "6.0.10",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
- "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
+ "version": "6.0.13",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
+ "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
"dev": true,
"requires": {
"cssesc": "^3.0.0",
@@ -27824,12 +28590,12 @@
"dev": true
},
"pretty-format": {
- "version": "29.5.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz",
- "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==",
+ "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,
"requires": {
- "@jest/schemas": "^29.4.3",
+ "@jest/schemas": "^29.6.3",
"ansi-styles": "^5.0.0",
"react-is": "^18.0.0"
},
@@ -27941,9 +28707,9 @@
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"pure-rand": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.0.tgz",
- "integrity": "sha512-rLSBxJjP+4DQOgcJAx6RZHT2he2pkhQdSnofG5VWyVl6GRq/K02ISOuOLcsMOrtKDIJb8JN2zm3FFzWNbezdPw==",
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.3.tgz",
+ "integrity": "sha512-KddyFewCsO0j3+np81IQ+SweXLDnDQTs5s67BOnrYmYe/yNmUhttQyGsYzy8yUnoljGAQ9sl38YB4vH8ur7Y+w==",
"dev": true
},
"qrcode": {
@@ -28094,11 +28860,6 @@
"strict-uri-encode": "^1.0.0"
}
},
- "querystring": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
- "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
- },
"querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
@@ -28176,9 +28937,9 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"readable-stream": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
- "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -28209,12 +28970,9 @@
"integrity": "sha512-Mb2WZ2bJF597exiqX7owBzrqJ74DHLK3yOQjCyPAaNifRncE8OD0wFIuoMhXxTnHK07+8zZ2SJEKy/qtiyR7vw=="
},
"redux": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
- "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
- "requires": {
- "@babel/runtime": "^7.9.2"
- }
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+ "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
},
"regenerate": {
"version": "1.4.2",
@@ -28237,23 +28995,23 @@
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
},
"regenerator-transform": {
- "version": "0.15.1",
- "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz",
- "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==",
+ "version": "0.15.2",
+ "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz",
+ "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==",
"dev": true,
"requires": {
"@babel/runtime": "^7.8.4"
}
},
"regexp.prototype.flags": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
- "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
+ "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.3",
- "functions-have-names": "^1.2.2"
+ "define-properties": "^1.2.0",
+ "set-function-name": "^2.0.0"
}
},
"regexpp": {
@@ -28354,11 +29112,11 @@
"dev": true
},
"resolve": {
- "version": "1.22.1",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
- "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+ "version": "1.22.8",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+ "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"requires": {
- "is-core-module": "^2.9.0",
+ "is-core-module": "^2.13.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
}
@@ -28392,9 +29150,9 @@
"dev": true
},
"resolve.exports": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.1.tgz",
- "integrity": "sha512-OEJWVeimw8mgQuj3HfkNl4KqRevH7lzeQNaWRPfx0PPse7Jk6ozcsG4FKVgtzDsC1KUF+YlTHh17NcgHOPykLw==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz",
+ "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==",
"dev": true
},
"responselike": {
@@ -28458,6 +29216,26 @@
"resolved": "https://registry.npmjs.org/rustbn.js/-/rustbn.js-0.2.0.tgz",
"integrity": "sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA=="
},
+ "safe-array-concat": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz",
+ "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.2.1",
+ "has-symbols": "^1.0.3",
+ "isarray": "^2.0.5"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true
+ }
+ }
+ },
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
@@ -28488,9 +29266,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sass": {
- "version": "1.62.1",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.62.1.tgz",
- "integrity": "sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A==",
+ "version": "1.69.7",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz",
+ "integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==",
"dev": true,
"requires": {
"chokidar": ">=3.0.0 <4.0.0",
@@ -28499,12 +29277,11 @@
}
},
"sass-loader": {
- "version": "13.3.0",
- "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.0.tgz",
- "integrity": "sha512-LeWNswSEujsZnwdn9AuA+Q5wZEAFlU+eORQsDKp35OtGAfFxYxpfk/Ylon+TGGkazSqxi2EHDTqdr3di8r7nCg==",
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.0.0.tgz",
+ "integrity": "sha512-oceP9wWbep/yRJ2+sMbCzk0UsXsDzdNis+N8nu9i5GwPXjy6v3DNB6TqfJLSpPO9k4+B8x8p/CEgjA9ZLkoLug==",
"dev": true,
"requires": {
- "klona": "^2.0.6",
"neo-async": "^2.6.2"
}
},
@@ -28527,15 +29304,15 @@
}
},
"schema-utils": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz",
- "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz",
+ "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.9",
- "ajv": "^8.8.0",
+ "ajv": "^8.9.0",
"ajv-formats": "^2.1.1",
- "ajv-keywords": "^5.0.0"
+ "ajv-keywords": "^5.1.0"
},
"dependencies": {
"ajv": {
@@ -28598,9 +29375,9 @@
"integrity": "sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA=="
},
"semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
},
"send": {
"version": "0.18.0",
@@ -28645,9 +29422,9 @@
}
},
"serialize-javascript": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
- "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==",
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
+ "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
"dev": true,
"requires": {
"randombytes": "^2.1.0"
@@ -28681,6 +29458,28 @@
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
+ "set-function-length": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz",
+ "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==",
+ "requires": {
+ "define-data-property": "^1.1.1",
+ "get-intrinsic": "^1.2.1",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.0"
+ }
+ },
+ "set-function-name": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz",
+ "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==",
+ "dev": true,
+ "requires": {
+ "define-data-property": "^1.0.1",
+ "functions-have-names": "^1.2.3",
+ "has-property-descriptors": "^1.0.0"
+ }
+ },
"set-immediate-shim": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
@@ -28919,26 +29718,37 @@
"strip-ansi": "^6.0.1"
}
},
+ "string.prototype.trim": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz",
+ "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
+ }
+ },
"string.prototype.trimend": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
- "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz",
+ "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
}
},
"string.prototype.trimstart": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
- "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz",
+ "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
}
},
"strip-ansi": {
@@ -28977,9 +29787,9 @@
"dev": true
},
"style-loader": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz",
- "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==",
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz",
+ "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==",
"dev": true,
"requires": {}
},
@@ -29108,9 +29918,9 @@
}
},
"sweetalert2": {
- "version": "11.7.5",
- "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.7.5.tgz",
- "integrity": "sha512-RBPKdK8Uf9/bz9r4vy7x6sGqf0MioSXt1po1lAwwcl3AwbjHVTc5S0yud4ZJKU1EhvJFVDrFoqIpHwWERwZJEA=="
+ "version": "11.10.3",
+ "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.3.tgz",
+ "integrity": "sha512-mZYtQR7v+khyEruq0SsVUa6XIdI9Aue8s2XAIpAwdlLN1T0w7mxKEjyubiBZ3/bLbHC/wGS4wNABvXWubCizvA=="
},
"symbol-tree": {
"version": "3.2.4",
@@ -29290,47 +30100,14 @@
"punycode": "^2.1.1"
}
},
- "ts-node": {
- "version": "10.9.1",
- "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
- "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
- "dev": true,
- "optional": true,
- "peer": true,
- "requires": {
- "@cspotcode/source-map-support": "^0.8.0",
- "@tsconfig/node10": "^1.0.7",
- "@tsconfig/node12": "^1.0.7",
- "@tsconfig/node14": "^1.0.0",
- "@tsconfig/node16": "^1.0.2",
- "acorn": "^8.4.1",
- "acorn-walk": "^8.1.1",
- "arg": "^4.1.0",
- "create-require": "^1.1.0",
- "diff": "^4.0.1",
- "make-error": "^1.1.1",
- "v8-compile-cache-lib": "^3.0.1",
- "yn": "3.1.1"
- },
- "dependencies": {
- "acorn-walk": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
- "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
- "dev": true,
- "optional": true,
- "peer": true
- }
- }
- },
"tsconfig-paths": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
- "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
+ "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==",
"dev": true,
"requires": {
"@types/json5": "^0.0.29",
- "json5": "^1.0.1",
+ "json5": "^1.0.2",
"minimist": "^1.2.6",
"strip-bom": "^3.0.0"
},
@@ -29404,6 +30181,42 @@
"mime-types": "~2.1.24"
}
},
+ "typed-array-buffer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz",
+ "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.2.1",
+ "is-typed-array": "^1.1.10"
+ }
+ },
+ "typed-array-byte-length": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz",
+ "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "has-proto": "^1.0.1",
+ "is-typed-array": "^1.1.10"
+ }
+ },
+ "typed-array-byte-offset": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz",
+ "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==",
+ "dev": true,
+ "requires": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "has-proto": "^1.0.1",
+ "is-typed-array": "^1.1.10"
+ }
+ },
"typed-array-length": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
@@ -29423,14 +30236,6 @@
"is-typedarray": "^1.0.0"
}
},
- "typescript": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.3.tgz",
- "integrity": "sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA==",
- "dev": true,
- "optional": true,
- "peer": true
- },
"ultron": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
@@ -29476,6 +30281,12 @@
"integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==",
"dev": true
},
+ "unicorn-magic": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz",
+ "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==",
+ "dev": true
+ },
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
@@ -29487,9 +30298,9 @@
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
},
"update-browserslist-db": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
- "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
+ "version": "1.0.13",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
+ "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
"requires": {
"escalade": "^3.1.1",
"picocolors": "^1.0.0"
@@ -29509,18 +30320,26 @@
"integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ=="
},
"url": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
- "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+ "version": "0.11.3",
+ "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz",
+ "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==",
"requires": {
- "punycode": "1.3.2",
- "querystring": "0.2.0"
+ "punycode": "^1.4.1",
+ "qs": "^6.11.2"
},
"dependencies": {
"punycode": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
- "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="
+ },
+ "qs": {
+ "version": "6.11.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
+ "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==",
+ "requires": {
+ "side-channel": "^1.0.4"
+ }
}
}
},
@@ -29579,14 +30398,6 @@
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
},
- "v8-compile-cache-lib": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
- "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
- "dev": true,
- "optional": true,
- "peer": true
- },
"v8-to-istanbul": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz",
@@ -29619,9 +30430,9 @@
}
},
"viewerjs": {
- "version": "1.11.3",
- "resolved": "https://registry.npmjs.org/viewerjs/-/viewerjs-1.11.3.tgz",
- "integrity": "sha512-efG3U61Umuj/1x6JAtdvnY9m407C/RkrkFilsMcLEWKDivpjNU3/FeL+feCY1Vkur9aQeBJ+z6K4CCPP7hv6vA=="
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/viewerjs/-/viewerjs-1.11.6.tgz",
+ "integrity": "sha512-TlhdSp2oEOLFXvEp4psKaeTjR5zBjTRcM/sHUN8PkV1UWuY8HKC8n7GaVdW5Xqnwdr/F1OmzLik1QwDjI4w/nw=="
},
"w3c-hr-time": {
"version": "1.0.2",
@@ -29661,23 +30472,23 @@
}
},
"web3": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3/-/web3-1.10.0.tgz",
- "integrity": "sha512-YfKY9wSkGcM8seO+daR89oVTcbu18NsVfvOngzqMYGUU0pPSQmE57qQDvQzUeoIOHAnXEBNzrhjQJmm8ER0rng==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3/-/web3-1.10.3.tgz",
+ "integrity": "sha512-DgUdOOqC/gTqW+VQl1EdPxrVRPB66xVNtuZ5KD4adVBtko87hkgM8BTZ0lZ8IbUfnQk6DyjcDujMiH3oszllAw==",
"requires": {
- "web3-bzz": "1.10.0",
- "web3-core": "1.10.0",
- "web3-eth": "1.10.0",
- "web3-eth-personal": "1.10.0",
- "web3-net": "1.10.0",
- "web3-shh": "1.10.0",
- "web3-utils": "1.10.0"
+ "web3-bzz": "1.10.3",
+ "web3-core": "1.10.3",
+ "web3-eth": "1.10.3",
+ "web3-eth-personal": "1.10.3",
+ "web3-net": "1.10.3",
+ "web3-shh": "1.10.3",
+ "web3-utils": "1.10.3"
}
},
"web3-bzz": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.10.0.tgz",
- "integrity": "sha512-o9IR59io3pDUsXTsps5pO5hW1D5zBmg46iNc2t4j2DkaYHNdDLwk2IP9ukoM2wg47QILfPEJYzhTfkS/CcX0KA==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.10.3.tgz",
+ "integrity": "sha512-XDIRsTwekdBXtFytMpHBuun4cK4x0ZMIDXSoo1UVYp+oMyZj07c7gf7tNQY5qZ/sN+CJIas4ilhN25VJcjSijQ==",
"requires": {
"@types/node": "^12.12.6",
"got": "12.1.0",
@@ -29692,23 +30503,23 @@
}
},
"web3-core": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.10.0.tgz",
- "integrity": "sha512-fWySwqy2hn3TL89w5TM8wXF1Z2Q6frQTKHWmP0ppRQorEK8NcHJRfeMiv/mQlSKoTS1F6n/nv2uyZsixFycjYQ==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.10.3.tgz",
+ "integrity": "sha512-Vbk0/vUNZxJlz3RFjAhNNt7qTpX8yE3dn3uFxfX5OHbuon5u65YEOd3civ/aQNW745N0vGUlHFNxxmn+sG9DIw==",
"requires": {
"@types/bn.js": "^5.1.1",
"@types/node": "^12.12.6",
"bignumber.js": "^9.0.0",
- "web3-core-helpers": "1.10.0",
- "web3-core-method": "1.10.0",
- "web3-core-requestmanager": "1.10.0",
- "web3-utils": "1.10.0"
+ "web3-core-helpers": "1.10.3",
+ "web3-core-method": "1.10.3",
+ "web3-core-requestmanager": "1.10.3",
+ "web3-utils": "1.10.3"
},
"dependencies": {
"@types/bn.js": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz",
- "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==",
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.3.tgz",
+ "integrity": "sha512-wT1B4iIO82ecXkdN6waCK8Ou7E71WU+mP1osDA5Q8c6Ur+ozU2vIKUIhSpUr6uE5L2YHocKS1Z2jG2fBC1YVeg==",
"requires": {
"@types/node": "*"
}
@@ -29721,98 +30532,98 @@
}
},
"web3-core-helpers": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.10.0.tgz",
- "integrity": "sha512-pIxAzFDS5vnbXvfvLSpaA1tfRykAe9adw43YCKsEYQwH0gCLL0kMLkaCX3q+Q8EVmAh+e1jWL/nl9U0de1+++g==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.10.3.tgz",
+ "integrity": "sha512-Yv7dQC3B9ipOc5sWm3VAz1ys70Izfzb8n9rSiQYIPjpqtJM+3V4EeK6ghzNR6CO2es0+Yu9CtCkw0h8gQhrTxA==",
"requires": {
- "web3-eth-iban": "1.10.0",
- "web3-utils": "1.10.0"
+ "web3-eth-iban": "1.10.3",
+ "web3-utils": "1.10.3"
}
},
"web3-core-method": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.10.0.tgz",
- "integrity": "sha512-4R700jTLAMKDMhQ+nsVfIXvH6IGJlJzGisIfMKWAIswH31h5AZz7uDUW2YctI+HrYd+5uOAlS4OJeeT9bIpvkA==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.10.3.tgz",
+ "integrity": "sha512-VZ/Dmml4NBmb0ep5PTSg9oqKoBtG0/YoMPei/bq/tUdlhB2dMB79sbeJPwx592uaV0Vpk7VltrrrBv5hTM1y4Q==",
"requires": {
"@ethersproject/transactions": "^5.6.2",
- "web3-core-helpers": "1.10.0",
- "web3-core-promievent": "1.10.0",
- "web3-core-subscriptions": "1.10.0",
- "web3-utils": "1.10.0"
+ "web3-core-helpers": "1.10.3",
+ "web3-core-promievent": "1.10.3",
+ "web3-core-subscriptions": "1.10.3",
+ "web3-utils": "1.10.3"
}
},
"web3-core-promievent": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.10.0.tgz",
- "integrity": "sha512-68N7k5LWL5R38xRaKFrTFT2pm2jBNFaM4GioS00YjAKXRQ3KjmhijOMG3TICz6Aa5+6GDWYelDNx21YAeZ4YTg==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.10.3.tgz",
+ "integrity": "sha512-HgjY+TkuLm5uTwUtaAfkTgRx/NzMxvVradCi02gy17NxDVdg/p6svBHcp037vcNpkuGeFznFJgULP+s2hdVgUQ==",
"requires": {
"eventemitter3": "4.0.4"
}
},
"web3-core-requestmanager": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.10.0.tgz",
- "integrity": "sha512-3z/JKE++Os62APml4dvBM+GAuId4h3L9ckUrj7ebEtS2AR0ixyQPbrBodgL91Sv7j7cQ3Y+hllaluqjguxvSaQ==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.10.3.tgz",
+ "integrity": "sha512-VT9sKJfgM2yBOIxOXeXiDuFMP4pxzF6FT+y8KTLqhDFHkbG3XRe42Vm97mB/IvLQCJOmokEjl3ps8yP1kbggyw==",
"requires": {
"util": "^0.12.5",
- "web3-core-helpers": "1.10.0",
- "web3-providers-http": "1.10.0",
- "web3-providers-ipc": "1.10.0",
- "web3-providers-ws": "1.10.0"
+ "web3-core-helpers": "1.10.3",
+ "web3-providers-http": "1.10.3",
+ "web3-providers-ipc": "1.10.3",
+ "web3-providers-ws": "1.10.3"
}
},
"web3-core-subscriptions": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.10.0.tgz",
- "integrity": "sha512-HGm1PbDqsxejI075gxBc5OSkwymilRWZufIy9zEpnWKNmfbuv5FfHgW1/chtJP6aP3Uq2vHkvTDl3smQBb8l+g==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.10.3.tgz",
+ "integrity": "sha512-KW0Mc8sgn70WadZu7RjQ4H5sNDJ5Lx8JMI3BWos+f2rW0foegOCyWhRu33W1s6ntXnqeBUw5rRCXZRlA3z+HNA==",
"requires": {
"eventemitter3": "4.0.4",
- "web3-core-helpers": "1.10.0"
+ "web3-core-helpers": "1.10.3"
}
},
"web3-eth": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.10.0.tgz",
- "integrity": "sha512-Z5vT6slNMLPKuwRyKGbqeGYC87OAy8bOblaqRTgg94CXcn/mmqU7iPIlG4506YdcdK3x6cfEDG7B6w+jRxypKA==",
- "requires": {
- "web3-core": "1.10.0",
- "web3-core-helpers": "1.10.0",
- "web3-core-method": "1.10.0",
- "web3-core-subscriptions": "1.10.0",
- "web3-eth-abi": "1.10.0",
- "web3-eth-accounts": "1.10.0",
- "web3-eth-contract": "1.10.0",
- "web3-eth-ens": "1.10.0",
- "web3-eth-iban": "1.10.0",
- "web3-eth-personal": "1.10.0",
- "web3-net": "1.10.0",
- "web3-utils": "1.10.0"
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.10.3.tgz",
+ "integrity": "sha512-Uk1U2qGiif2mIG8iKu23/EQJ2ksB1BQXy3wF3RvFuyxt8Ft9OEpmGlO7wOtAyJdoKzD5vcul19bJpPcWSAYZhA==",
+ "requires": {
+ "web3-core": "1.10.3",
+ "web3-core-helpers": "1.10.3",
+ "web3-core-method": "1.10.3",
+ "web3-core-subscriptions": "1.10.3",
+ "web3-eth-abi": "1.10.3",
+ "web3-eth-accounts": "1.10.3",
+ "web3-eth-contract": "1.10.3",
+ "web3-eth-ens": "1.10.3",
+ "web3-eth-iban": "1.10.3",
+ "web3-eth-personal": "1.10.3",
+ "web3-net": "1.10.3",
+ "web3-utils": "1.10.3"
}
},
"web3-eth-abi": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.10.0.tgz",
- "integrity": "sha512-cwS+qRBWpJ43aI9L3JS88QYPfFcSJJ3XapxOQ4j40v6mk7ATpA8CVK1vGTzpihNlOfMVRBkR95oAj7oL6aiDOg==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.10.3.tgz",
+ "integrity": "sha512-O8EvV67uhq0OiCMekqYsDtb6FzfYzMXT7VMHowF8HV6qLZXCGTdB/NH4nJrEh2mFtEwVdS6AmLFJAQd2kVyoMQ==",
"requires": {
"@ethersproject/abi": "^5.6.3",
- "web3-utils": "1.10.0"
+ "web3-utils": "1.10.3"
}
},
"web3-eth-accounts": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.10.0.tgz",
- "integrity": "sha512-wiq39Uc3mOI8rw24wE2n15hboLE0E9BsQLdlmsL4Zua9diDS6B5abXG0XhFcoNsXIGMWXVZz4TOq3u4EdpXF/Q==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.10.3.tgz",
+ "integrity": "sha512-8MipGgwusDVgn7NwKOmpeo3gxzzd+SmwcWeBdpXknuyDiZSQy9tXe+E9LeFGrmys/8mLLYP79n3jSbiTyv+6pQ==",
"requires": {
- "@ethereumjs/common": "2.5.0",
- "@ethereumjs/tx": "3.3.2",
+ "@ethereumjs/common": "2.6.5",
+ "@ethereumjs/tx": "3.5.2",
+ "@ethereumjs/util": "^8.1.0",
"eth-lib": "0.2.8",
- "ethereumjs-util": "^7.1.5",
"scrypt-js": "^3.0.1",
"uuid": "^9.0.0",
- "web3-core": "1.10.0",
- "web3-core-helpers": "1.10.0",
- "web3-core-method": "1.10.0",
- "web3-utils": "1.10.0"
+ "web3-core": "1.10.3",
+ "web3-core-helpers": "1.10.3",
+ "web3-core-method": "1.10.3",
+ "web3-utils": "1.10.3"
},
"dependencies": {
"bn.js": {
@@ -29831,31 +30642,31 @@
}
},
"uuid": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
- "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="
}
}
},
"web3-eth-contract": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.10.0.tgz",
- "integrity": "sha512-MIC5FOzP/+2evDksQQ/dpcXhSqa/2hFNytdl/x61IeWxhh6vlFeSjq0YVTAyIzdjwnL7nEmZpjfI6y6/Ufhy7w==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.10.3.tgz",
+ "integrity": "sha512-Y2CW61dCCyY4IoUMD4JsEQWrILX4FJWDWC/Txx/pr3K/+fGsBGvS9kWQN5EsVXOp4g7HoFOfVh9Lf7BmVVSRmg==",
"requires": {
"@types/bn.js": "^5.1.1",
- "web3-core": "1.10.0",
- "web3-core-helpers": "1.10.0",
- "web3-core-method": "1.10.0",
- "web3-core-promievent": "1.10.0",
- "web3-core-subscriptions": "1.10.0",
- "web3-eth-abi": "1.10.0",
- "web3-utils": "1.10.0"
+ "web3-core": "1.10.3",
+ "web3-core-helpers": "1.10.3",
+ "web3-core-method": "1.10.3",
+ "web3-core-promievent": "1.10.3",
+ "web3-core-subscriptions": "1.10.3",
+ "web3-eth-abi": "1.10.3",
+ "web3-utils": "1.10.3"
},
"dependencies": {
"@types/bn.js": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz",
- "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==",
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.3.tgz",
+ "integrity": "sha512-wT1B4iIO82ecXkdN6waCK8Ou7E71WU+mP1osDA5Q8c6Ur+ozU2vIKUIhSpUr6uE5L2YHocKS1Z2jG2fBC1YVeg==",
"requires": {
"@types/node": "*"
}
@@ -29863,40 +30674,40 @@
}
},
"web3-eth-ens": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.10.0.tgz",
- "integrity": "sha512-3hpGgzX3qjgxNAmqdrC2YUQMTfnZbs4GeLEmy8aCWziVwogbuqQZ+Gzdfrym45eOZodk+lmXyLuAdqkNlvkc1g==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.10.3.tgz",
+ "integrity": "sha512-hR+odRDXGqKemw1GFniKBEXpjYwLgttTES+bc7BfTeoUyUZXbyDHe5ifC+h+vpzxh4oS0TnfcIoarK0Z9tFSiQ==",
"requires": {
"content-hash": "^2.5.2",
"eth-ens-namehash": "2.0.8",
- "web3-core": "1.10.0",
- "web3-core-helpers": "1.10.0",
- "web3-core-promievent": "1.10.0",
- "web3-eth-abi": "1.10.0",
- "web3-eth-contract": "1.10.0",
- "web3-utils": "1.10.0"
+ "web3-core": "1.10.3",
+ "web3-core-helpers": "1.10.3",
+ "web3-core-promievent": "1.10.3",
+ "web3-eth-abi": "1.10.3",
+ "web3-eth-contract": "1.10.3",
+ "web3-utils": "1.10.3"
}
},
"web3-eth-iban": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.10.0.tgz",
- "integrity": "sha512-0l+SP3IGhInw7Q20LY3IVafYEuufo4Dn75jAHT7c2aDJsIolvf2Lc6ugHkBajlwUneGfbRQs/ccYPQ9JeMUbrg==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.10.3.tgz",
+ "integrity": "sha512-ZCfOjYKAjaX2TGI8uif5ah+J3BYFuo+47JOIV1RIz2l7kD9VfnxvRH5UiQDRyMALQC7KFd2hUqIEtHklapNyKA==",
"requires": {
"bn.js": "^5.2.1",
- "web3-utils": "1.10.0"
+ "web3-utils": "1.10.3"
}
},
"web3-eth-personal": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.10.0.tgz",
- "integrity": "sha512-anseKn98w/d703eWq52uNuZi7GhQeVjTC5/svrBWEKob0WZ5kPdo+EZoFN0sp5a5ubbrk/E0xSl1/M5yORMtpg==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.10.3.tgz",
+ "integrity": "sha512-avrQ6yWdADIvuNQcFZXmGLCEzulQa76hUOuVywN7O3cklB4nFc/Gp3yTvD3bOAaE7DhjLQfhUTCzXL7WMxVTsw==",
"requires": {
"@types/node": "^12.12.6",
- "web3-core": "1.10.0",
- "web3-core-helpers": "1.10.0",
- "web3-core-method": "1.10.0",
- "web3-net": "1.10.0",
- "web3-utils": "1.10.0"
+ "web3-core": "1.10.3",
+ "web3-core-helpers": "1.10.3",
+ "web3-core-method": "1.10.3",
+ "web3-net": "1.10.3",
+ "web3-utils": "1.10.3"
},
"dependencies": {
"@types/node": {
@@ -29907,13 +30718,13 @@
}
},
"web3-net": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.10.0.tgz",
- "integrity": "sha512-NLH/N3IshYWASpxk4/18Ge6n60GEvWBVeM8inx2dmZJVmRI6SJIlUxbL8jySgiTn3MMZlhbdvrGo8fpUW7a1GA==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.10.3.tgz",
+ "integrity": "sha512-IoSr33235qVoI1vtKssPUigJU9Fc/Ph0T9CgRi15sx+itysmvtlmXMNoyd6Xrgm9LuM4CIhxz7yDzH93B79IFg==",
"requires": {
- "web3-core": "1.10.0",
- "web3-core-method": "1.10.0",
- "web3-utils": "1.10.0"
+ "web3-core": "1.10.3",
+ "web3-core-method": "1.10.3",
+ "web3-utils": "1.10.3"
}
},
"web3-provider-engine": {
@@ -29997,68 +30808,82 @@
}
},
"web3-providers-http": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.10.0.tgz",
- "integrity": "sha512-eNr965YB8a9mLiNrkjAWNAPXgmQWfpBfkkn7tpEFlghfww0u3I0tktMZiaToJVcL2+Xq+81cxbkpeWJ5XQDwOA==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.10.3.tgz",
+ "integrity": "sha512-6dAgsHR3MxJ0Qyu3QLFlQEelTapVfWNTu5F45FYh8t7Y03T1/o+YAkVxsbY5AdmD+y5bXG/XPJ4q8tjL6MgZHw==",
"requires": {
- "abortcontroller-polyfill": "^1.7.3",
- "cross-fetch": "^3.1.4",
+ "abortcontroller-polyfill": "^1.7.5",
+ "cross-fetch": "^4.0.0",
"es6-promise": "^4.2.8",
- "web3-core-helpers": "1.10.0"
+ "web3-core-helpers": "1.10.3"
},
"dependencies": {
"cross-fetch": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
- "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
+ "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
"requires": {
- "node-fetch": "2.6.7"
+ "node-fetch": "^2.6.12"
}
}
}
},
"web3-providers-ipc": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.10.0.tgz",
- "integrity": "sha512-OfXG1aWN8L1OUqppshzq8YISkWrYHaATW9H8eh0p89TlWMc1KZOL9vttBuaBEi96D/n0eYDn2trzt22bqHWfXA==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.10.3.tgz",
+ "integrity": "sha512-vP5WIGT8FLnGRfswTxNs9rMfS1vCbMezj/zHbBe/zB9GauBRTYVrUo2H/hVrhLg8Ut7AbsKZ+tCJ4mAwpKi2hA==",
"requires": {
"oboe": "2.1.5",
- "web3-core-helpers": "1.10.0"
+ "web3-core-helpers": "1.10.3"
}
},
"web3-providers-ws": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.10.0.tgz",
- "integrity": "sha512-sK0fNcglW36yD5xjnjtSGBnEtf59cbw4vZzJ+CmOWIKGIR96mP5l684g0WD0Eo+f4NQc2anWWXG74lRc9OVMCQ==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.10.3.tgz",
+ "integrity": "sha512-/filBXRl48INxsh6AuCcsy4v5ndnTZ/p6bl67kmO9aK1wffv7CT++DrtclDtVMeDGCgB3van+hEf9xTAVXur7Q==",
"requires": {
"eventemitter3": "4.0.4",
- "web3-core-helpers": "1.10.0",
+ "web3-core-helpers": "1.10.3",
"websocket": "^1.0.32"
}
},
"web3-shh": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.10.0.tgz",
- "integrity": "sha512-uNUUuNsO2AjX41GJARV9zJibs11eq6HtOe6Wr0FtRUcj8SN6nHeYIzwstAvJ4fXA53gRqFMTxdntHEt9aXVjpg==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.10.3.tgz",
+ "integrity": "sha512-cAZ60CPvs9azdwMSQ/PSUdyV4PEtaW5edAZhu3rCXf6XxQRliBboic+AvwUvB6j3eswY50VGa5FygfVmJ1JVng==",
"requires": {
- "web3-core": "1.10.0",
- "web3-core-method": "1.10.0",
- "web3-core-subscriptions": "1.10.0",
- "web3-net": "1.10.0"
+ "web3-core": "1.10.3",
+ "web3-core-method": "1.10.3",
+ "web3-core-subscriptions": "1.10.3",
+ "web3-net": "1.10.3"
}
},
"web3-utils": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.0.tgz",
- "integrity": "sha512-kSaCM0uMcZTNUSmn5vMEhlo02RObGNRRCkdX0V9UTAU0+lrvn0HSaudyCo6CQzuXUsnuY2ERJGCGPfeWmv19Rg==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.3.tgz",
+ "integrity": "sha512-OqcUrEE16fDBbGoQtZXWdavsPzbGIDc5v3VrRTZ0XrIpefC/viZ1ZU9bGEemazyS0catk/3rkOOxpzTfY+XsyQ==",
"requires": {
+ "@ethereumjs/util": "^8.1.0",
"bn.js": "^5.2.1",
"ethereum-bloom-filters": "^1.0.6",
- "ethereumjs-util": "^7.1.0",
+ "ethereum-cryptography": "^2.1.2",
"ethjs-unit": "0.1.6",
"number-to-bn": "1.7.0",
"randombytes": "^2.1.0",
"utf8": "3.0.0"
+ },
+ "dependencies": {
+ "ethereum-cryptography": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz",
+ "integrity": "sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug==",
+ "requires": {
+ "@noble/curves": "1.1.0",
+ "@noble/hashes": "1.3.1",
+ "@scure/bip32": "1.3.1",
+ "@scure/bip39": "1.2.1"
+ }
+ }
}
},
"web3modal": {
@@ -30081,9 +30906,9 @@
"dev": true
},
"webpack": {
- "version": "5.83.1",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.83.1.tgz",
- "integrity": "sha512-TNsG9jDScbNuB+Lb/3+vYolPplCS3bbEaJf+Bj0Gw4DhP3ioAflBb1flcRt9zsWITyvOhM96wMQNRWlSX52DgA==",
+ "version": "5.89.0",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz",
+ "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==",
"dev": true,
"requires": {
"@types/eslint-scope": "^3.7.3",
@@ -30092,10 +30917,10 @@
"@webassemblyjs/wasm-edit": "^1.11.5",
"@webassemblyjs/wasm-parser": "^1.11.5",
"acorn": "^8.7.1",
- "acorn-import-assertions": "^1.7.6",
+ "acorn-import-assertions": "^1.9.0",
"browserslist": "^4.14.5",
"chrome-trace-event": "^1.0.2",
- "enhanced-resolve": "^5.14.0",
+ "enhanced-resolve": "^5.15.0",
"es-module-lexer": "^1.2.1",
"eslint-scope": "5.1.1",
"events": "^3.2.0",
@@ -30105,24 +30930,17 @@
"loader-runner": "^4.2.0",
"mime-types": "^2.1.27",
"neo-async": "^2.6.2",
- "schema-utils": "^3.1.2",
+ "schema-utils": "^3.2.0",
"tapable": "^2.1.1",
"terser-webpack-plugin": "^5.3.7",
"watchpack": "^2.4.0",
"webpack-sources": "^3.2.3"
},
"dependencies": {
- "acorn-import-assertions": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
- "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
- "dev": true,
- "requires": {}
- },
"schema-utils": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz",
- "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+ "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.8",
@@ -30139,15 +30957,15 @@
}
},
"webpack-cli": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.1.tgz",
- "integrity": "sha512-OLJwVMoXnXYH2ncNGU8gxVpUtm3ybvdioiTvHgUyBuyMLKiVvWy+QObzBsMtp5pH7qQoEuWgeEUQ/sU3ZJFzAw==",
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz",
+ "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==",
"dev": true,
"requires": {
"@discoveryjs/json-ext": "^0.5.0",
- "@webpack-cli/configtest": "^2.1.0",
- "@webpack-cli/info": "^2.0.1",
- "@webpack-cli/serve": "^2.0.4",
+ "@webpack-cli/configtest": "^2.1.1",
+ "@webpack-cli/info": "^2.0.2",
+ "@webpack-cli/serve": "^2.0.5",
"colorette": "^2.0.14",
"commander": "^10.0.1",
"cross-spawn": "^7.0.3",
@@ -30274,16 +31092,15 @@
"integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q=="
},
"which-typed-array": {
- "version": "1.1.9",
- "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz",
- "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==",
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz",
+ "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==",
"requires": {
"available-typed-arrays": "^1.0.5",
- "call-bind": "^1.0.2",
+ "call-bind": "^1.0.4",
"for-each": "^0.3.3",
"gopd": "^1.0.1",
- "has-tostringtag": "^1.0.0",
- "is-typed-array": "^1.1.10"
+ "has-tostringtag": "^1.0.0"
}
},
"wildcard": {
@@ -30293,9 +31110,9 @@
"dev": true
},
"word-wrap": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
+ "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"dev": true
},
"wrap-ansi": {
@@ -30448,9 +31265,9 @@
"dev": true
},
"yargs": {
- "version": "17.7.1",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz",
- "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==",
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dev": true,
"requires": {
"cliui": "^8.0.1",
@@ -30476,14 +31293,6 @@
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true
},
- "yn": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
- "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
- "dev": true,
- "optional": true,
- "peer": true
- },
"yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json
index a668f06934d4..86e38b7df8f5 100644
--- a/apps/block_scout_web/assets/package.json
+++ b/apps/block_scout_web/assets/package.json
@@ -19,24 +19,24 @@
"eslint": "eslint js/**"
},
"dependencies": {
- "@fortawesome/fontawesome-free": "^6.4.0",
- "@amplitude/analytics-browser": "^1.10.3",
+ "@fortawesome/fontawesome-free": "^6.5.1",
+ "@amplitude/analytics-browser": "^2.3.8",
"@tarekraafat/autocomplete.js": "^10.2.7",
"@walletconnect/web3-provider": "^1.8.0",
- "assert": "^2.0.0",
- "bignumber.js": "^9.1.1",
+ "assert": "^2.1.0",
+ "bignumber.js": "^9.1.2",
"bootstrap": "^4.6.0",
- "chart.js": "^4.3.0",
+ "chart.js": "^4.4.1",
"chartjs-adapter-luxon": "^1.3.1",
"clipboard": "^2.0.11",
- "core-js": "^3.30.2",
+ "core-js": "^3.35.0",
"crypto-browserify": "^3.12.0",
"dropzone": "^5.9.3",
"eth-net-props": "^1.0.41",
- "highlight.js": "^11.8.0",
+ "highlight.js": "^11.9.0",
"https-browserify": "^1.0.0",
"humps": "^2.0.1",
- "jquery": "^3.7.0",
+ "jquery": "^3.7.1",
"js-cookie": "^3.0.5",
"lodash.debounce": "^4.0.8",
"lodash.differenceby": "^4.8.0",
@@ -56,56 +56,56 @@
"lodash.omit": "^4.5.0",
"lodash.rangeright": "^4.2.0",
"lodash.reduce": "^4.6.0",
- "luxon": "^3.3.0",
+ "luxon": "^3.4.4",
"malihu-custom-scrollbar-plugin": "3.1.5",
- "mixpanel-browser": "^2.47.0",
- "moment": "^2.29.4",
+ "mixpanel-browser": "^2.48.1",
+ "moment": "^2.30.1",
"nanomorph": "^5.4.0",
"numeral": "^2.0.6",
"os-browserify": "^0.3.0",
"path-parser": "^6.1.0",
"phoenix": "file:../../../deps/phoenix",
"phoenix_html": "file:../../../deps/phoenix_html",
- "photoswipe": "^5.3.7",
+ "photoswipe": "^5.4.3",
"pikaday": "^1.8.2",
"popper.js": "^1.14.7",
"reduce-reducers": "^1.0.4",
- "redux": "^4.2.1",
+ "redux": "^5.0.1",
"stream-browserify": "^3.0.0",
"stream-http": "^3.1.1",
- "sweetalert2": "^11.7.5",
+ "sweetalert2": "^11.10.3",
"urijs": "^1.19.11",
- "url": "^0.11.0",
+ "url": "^0.11.3",
"util": "^0.12.5",
- "viewerjs": "^1.11.3",
- "web3": "^1.10.0",
+ "viewerjs": "^1.11.6",
+ "web3": "^1.10.3",
"web3modal": "^1.9.12",
"xss": "^1.0.14"
},
"devDependencies": {
- "@babel/core": "^7.21.8",
- "@babel/preset-env": "^7.21.5",
- "autoprefixer": "^10.4.14",
- "babel-loader": "^9.1.2",
- "copy-webpack-plugin": "^11.0.0",
- "css-loader": "^5.2.7",
- "css-minimizer-webpack-plugin": "^5.0.0",
- "eslint": "^8.41.0",
- "eslint-config-standard": "^17.0.0",
- "eslint-plugin-import": "^2.27.5",
+ "@babel/core": "^7.23.7",
+ "@babel/preset-env": "^7.23.8",
+ "autoprefixer": "^10.4.16",
+ "babel-loader": "^9.1.3",
+ "copy-webpack-plugin": "^12.0.1",
+ "css-loader": "^6.9.0",
+ "css-minimizer-webpack-plugin": "^5.0.1",
+ "eslint": "^8.56.0",
+ "eslint-config-standard": "^17.1.0",
+ "eslint-plugin-import": "^2.29.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.1.1",
"file-loader": "^6.2.0",
- "jest": "^29.5.0",
- "jest-environment-jsdom": "^29.5.0",
- "mini-css-extract-plugin": "^2.7.6",
- "postcss": "^8.4.23",
- "postcss-loader": "^7.3.0",
- "sass": "^1.62.1",
- "sass-loader": "^13.3.0",
- "style-loader": "^3.3.3",
- "webpack": "^5.83.1",
- "webpack-cli": "^5.1.1"
+ "jest": "^29.7.0",
+ "jest-environment-jsdom": "^29.7.0",
+ "mini-css-extract-plugin": "^2.7.7",
+ "postcss": "^8.4.33",
+ "postcss-loader": "^7.3.4",
+ "sass": "^1.69.7",
+ "sass-loader": "^14.0.0",
+ "style-loader": "^3.3.4",
+ "webpack": "^5.89.0",
+ "webpack-cli": "^5.1.4"
},
"jest": {
"moduleNameMapper": {
diff --git a/apps/block_scout_web/assets/webpack.config.js b/apps/block_scout_web/assets/webpack.config.js
index 3978ca45f1a1..3b90aeec8e33 100644
--- a/apps/block_scout_web/assets/webpack.config.js
+++ b/apps/block_scout_web/assets/webpack.config.js
@@ -108,7 +108,10 @@ const appJs =
esModule: false,
},
}, {
- loader: 'css-loader'
+ loader: 'css-loader',
+ options: {
+ esModule: false,
+ },
}, {
loader: 'postcss-loader'
}, {
diff --git a/apps/block_scout_web/config/config.exs b/apps/block_scout_web/config/config.exs
index a8f34240d0f7..bb545430cbe8 100644
--- a/apps/block_scout_web/config/config.exs
+++ b/apps/block_scout_web/config/config.exs
@@ -5,11 +5,19 @@
# is restricted to this project.
import Config
+[__DIR__ | ~w(.. .. .. config config_helper.exs)]
+|> Path.join()
+|> Code.eval_file()
+
# General application configuration
config :block_scout_web,
namespace: BlockScoutWeb,
- ecto_repos: [Explorer.Repo, Explorer.Repo.Account],
- cookie_domain: System.get_env("SESSION_COOKIE_DOMAIN")
+ ecto_repos: ConfigHelper.repos(),
+ cookie_domain: System.get_env("SESSION_COOKIE_DOMAIN"),
+ # 604800 seconds, 1 week
+ session_cookie_ttl: 60 * 60 * 24 * 7,
+ invalid_session_key: "invalid_session",
+ api_v2_temp_token_key: "api_v2_temp_token"
config :block_scout_web,
admin_panel_enabled: System.get_env("ADMIN_PANEL_ENABLED", "") == "true"
diff --git a/apps/block_scout_web/lib/block_scout_web/api_key_v2_router.ex b/apps/block_scout_web/lib/block_scout_web/api_key_v2_router.ex
index 3a302b8a4761..a1b9943f9bc8 100644
--- a/apps/block_scout_web/lib/block_scout_web/api_key_v2_router.ex
+++ b/apps/block_scout_web/lib/block_scout_web/api_key_v2_router.ex
@@ -9,8 +9,6 @@ defmodule BlockScoutWeb.APIKeyV2Router do
plug(Logger, application: :api_v2)
plug(:accepts, ["json"])
plug(CheckApiV2)
- plug(:fetch_session)
- plug(:protect_from_forgery)
end
scope "/", as: :api_v2 do
@@ -18,6 +16,6 @@ defmodule BlockScoutWeb.APIKeyV2Router do
alias BlockScoutWeb.API.V2
- get("/", V2.APIKeyController, :get_key)
+ post("/", V2.APIKeyController, :get_key)
end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex
index fe8ad4b7e505..5f8415014aaa 100644
--- a/apps/block_scout_web/lib/block_scout_web/api_router.ex
+++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex
@@ -13,7 +13,7 @@ defmodule BlockScoutWeb.ApiRouter do
Router for API
"""
use BlockScoutWeb, :router
- alias BlockScoutWeb.{APIKeyV2Router, SmartContractsApiV2Router}
+ alias BlockScoutWeb.{AddressTransactionController, APIKeyV2Router, SmartContractsApiV2Router}
alias BlockScoutWeb.Plug.{CheckAccountAPI, CheckApiV2, RateLimit}
forward("/v2/smart-contracts", SmartContractsApiV2Router)
@@ -49,6 +49,7 @@ defmodule BlockScoutWeb.ApiRouter do
alias BlockScoutWeb.Account.Api.V1.{AuthenticateController, EmailController, TagsController, UserController}
alias BlockScoutWeb.API.V2
+ # TODO: Remove /account/v1 paths
scope "/account/v1", as: :account_v1 do
pipe_through(:api)
pipe_through(:account_api)
@@ -62,6 +63,70 @@ defmodule BlockScoutWeb.ApiRouter do
get("/resend", EmailController, :resend_email)
end
+ scope "/user" do
+ get("/info", UserController, :info)
+
+ get("/watchlist", UserController, :watchlist_old)
+ delete("/watchlist/:id", UserController, :delete_watchlist)
+ post("/watchlist", UserController, :create_watchlist)
+ put("/watchlist/:id", UserController, :update_watchlist)
+
+ get("/api_keys", UserController, :api_keys)
+ delete("/api_keys/:api_key", UserController, :delete_api_key)
+ post("/api_keys", UserController, :create_api_key)
+ put("/api_keys/:api_key", UserController, :update_api_key)
+
+ get("/custom_abis", UserController, :custom_abis)
+ delete("/custom_abis/:id", UserController, :delete_custom_abi)
+ post("/custom_abis", UserController, :create_custom_abi)
+ put("/custom_abis/:id", UserController, :update_custom_abi)
+
+ get("/public_tags", UserController, :public_tags_requests)
+ delete("/public_tags/:id", UserController, :delete_public_tags_request)
+ post("/public_tags", UserController, :create_public_tags_request)
+ put("/public_tags/:id", UserController, :update_public_tags_request)
+
+ scope "/tags" do
+ get("/address/", UserController, :tags_address_old)
+ get("/address/:id", UserController, :tags_address)
+ delete("/address/:id", UserController, :delete_tag_address)
+ post("/address/", UserController, :create_tag_address)
+ put("/address/:id", UserController, :update_tag_address)
+
+ get("/transaction/", UserController, :tags_transaction_old)
+ get("/transaction/:id", UserController, :tags_transaction)
+ delete("/transaction/:id", UserController, :delete_tag_transaction)
+ post("/transaction/", UserController, :create_tag_transaction)
+ put("/transaction/:id", UserController, :update_tag_transaction)
+ end
+ end
+ end
+
+ # TODO: Remove /account/v1 paths
+ scope "/account/v1" do
+ pipe_through(:api)
+ pipe_through(:account_api)
+
+ scope "/tags" do
+ get("/address/:address_hash", TagsController, :tags_address)
+
+ get("/transaction/:transaction_hash", TagsController, :tags_transaction)
+ end
+ end
+
+ scope "/account/v2", as: :account_v2 do
+ pipe_through(:api)
+ pipe_through(:account_api)
+
+ get("/authenticate", AuthenticateController, :authenticate_get)
+ post("/authenticate", AuthenticateController, :authenticate_post)
+
+ get("/get_csrf", UserController, :get_csrf)
+
+ scope "/email" do
+ get("/resend", EmailController, :resend_email)
+ end
+
scope "/user" do
get("/info", UserController, :info)
@@ -101,7 +166,7 @@ defmodule BlockScoutWeb.ApiRouter do
end
end
- scope "/account/v1" do
+ scope "/account/v2" do
pipe_through(:api)
pipe_through(:account_api)
@@ -116,6 +181,7 @@ defmodule BlockScoutWeb.ApiRouter do
pipe_through(:api_v2_no_session)
post("/token-info", V2.ImportController, :import_token_info)
+ get("/smart-contracts/:address_hash_param", V2.ImportController, :try_to_search_contract)
end
scope "/v2", as: :api_v2 do
@@ -124,6 +190,7 @@ defmodule BlockScoutWeb.ApiRouter do
scope "/search" do
get("/", V2.SearchController, :search)
get("/check-redirect", V2.SearchController, :check_redirect)
+ get("/quick", V2.SearchController, :quick_search)
end
scope "/config" do
@@ -134,12 +201,22 @@ defmodule BlockScoutWeb.ApiRouter do
scope "/transactions" do
get("/", V2.TransactionController, :transactions)
get("/watchlist", V2.TransactionController, :watchlist_transactions)
- get("/:transaction_hash", V2.TransactionController, :transaction)
- get("/:transaction_hash/token-transfers", V2.TransactionController, :token_transfers)
- get("/:transaction_hash/internal-transactions", V2.TransactionController, :internal_transactions)
- get("/:transaction_hash/logs", V2.TransactionController, :logs)
- get("/:transaction_hash/raw-trace", V2.TransactionController, :raw_trace)
- get("/:transaction_hash/state-changes", V2.TransactionController, :state_changes)
+
+ if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do
+ get("/zkevm-batch/:batch_number", V2.TransactionController, :zkevm_batch)
+ end
+
+ if System.get_env("CHAIN_TYPE") == "suave" do
+ get("/execution-node/:execution_node_hash_param", V2.TransactionController, :execution_node)
+ end
+
+ get("/:transaction_hash_param", V2.TransactionController, :transaction)
+ get("/:transaction_hash_param/token-transfers", V2.TransactionController, :token_transfers)
+ get("/:transaction_hash_param/internal-transactions", V2.TransactionController, :internal_transactions)
+ get("/:transaction_hash_param/logs", V2.TransactionController, :logs)
+ get("/:transaction_hash_param/raw-trace", V2.TransactionController, :raw_trace)
+ get("/:transaction_hash_param/state-changes", V2.TransactionController, :state_changes)
+ get("/:transaction_hash_param/summary", V2.TransactionController, :summary)
end
scope "/blocks" do
@@ -151,31 +228,34 @@ defmodule BlockScoutWeb.ApiRouter do
scope "/addresses" do
get("/", V2.AddressController, :addresses_list)
- get("/:address_hash", V2.AddressController, :address)
- get("/:address_hash/counters", V2.AddressController, :counters)
- get("/:address_hash/token-balances", V2.AddressController, :token_balances)
- get("/:address_hash/tokens", V2.AddressController, :tokens)
- get("/:address_hash/transactions", V2.AddressController, :transactions)
- get("/:address_hash/token-transfers", V2.AddressController, :token_transfers)
- get("/:address_hash/internal-transactions", V2.AddressController, :internal_transactions)
- get("/:address_hash/logs", V2.AddressController, :logs)
- get("/:address_hash/blocks-validated", V2.AddressController, :blocks_validated)
- get("/:address_hash/coin-balance-history", V2.AddressController, :coin_balance_history)
- get("/:address_hash/coin-balance-history-by-day", V2.AddressController, :coin_balance_history_by_day)
- get("/:address_hash/withdrawals", V2.AddressController, :withdrawals)
+ get("/:address_hash_param", V2.AddressController, :address)
+ get("/:address_hash_param/tabs-counters", V2.AddressController, :tabs_counters)
+ get("/:address_hash_param/counters", V2.AddressController, :counters)
+ get("/:address_hash_param/token-balances", V2.AddressController, :token_balances)
+ get("/:address_hash_param/tokens", V2.AddressController, :tokens)
+ get("/:address_hash_param/transactions", V2.AddressController, :transactions)
+ get("/:address_hash_param/token-transfers", V2.AddressController, :token_transfers)
+ get("/:address_hash_param/internal-transactions", V2.AddressController, :internal_transactions)
+ get("/:address_hash_param/logs", V2.AddressController, :logs)
+ get("/:address_hash_param/blocks-validated", V2.AddressController, :blocks_validated)
+ get("/:address_hash_param/coin-balance-history", V2.AddressController, :coin_balance_history)
+ get("/:address_hash_param/coin-balance-history-by-day", V2.AddressController, :coin_balance_history_by_day)
+ get("/:address_hash_param/withdrawals", V2.AddressController, :withdrawals)
+ get("/:address_hash_param/nft", V2.AddressController, :nft_list)
+ get("/:address_hash_param/nft/collections", V2.AddressController, :nft_collections)
end
scope "/tokens" do
get("/", V2.TokenController, :tokens_list)
- get("/:address_hash", V2.TokenController, :token)
- get("/:address_hash/counters", V2.TokenController, :counters)
- get("/:address_hash/transfers", V2.TokenController, :transfers)
- get("/:address_hash/holders", V2.TokenController, :holders)
- get("/:address_hash/instances", V2.TokenController, :instances)
- get("/:address_hash/instances/:token_id", V2.TokenController, :instance)
- get("/:address_hash/instances/:token_id/transfers", V2.TokenController, :transfers_by_instance)
- get("/:address_hash/instances/:token_id/holders", V2.TokenController, :holders_by_instance)
- get("/:address_hash/instances/:token_id/transfers-count", V2.TokenController, :transfers_count_by_instance)
+ get("/:address_hash_param", V2.TokenController, :token)
+ get("/:address_hash_param/counters", V2.TokenController, :counters)
+ get("/:address_hash_param/transfers", V2.TokenController, :transfers)
+ get("/:address_hash_param/holders", V2.TokenController, :holders)
+ get("/:address_hash_param/instances", V2.TokenController, :instances)
+ get("/:address_hash_param/instances/:token_id", V2.TokenController, :instance)
+ get("/:address_hash_param/instances/:token_id/transfers", V2.TokenController, :transfers_by_instance)
+ get("/:address_hash_param/instances/:token_id/holders", V2.TokenController, :holders_by_instance)
+ get("/:address_hash_param/instances/:token_id/transfers-count", V2.TokenController, :transfers_count_by_instance)
end
scope "/main-page" do
@@ -183,6 +263,11 @@ defmodule BlockScoutWeb.ApiRouter do
get("/transactions", V2.MainPageController, :transactions)
get("/transactions/watchlist", V2.MainPageController, :watchlist_transactions)
get("/indexing-status", V2.MainPageController, :indexing_status)
+
+ if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do
+ get("/zkevm/batches/confirmed", V2.ZkevmController, :batches_confirmed)
+ get("/zkevm/batches/latest-number", V2.ZkevmController, :batch_latest_number)
+ end
end
scope "/stats" do
@@ -194,22 +279,69 @@ defmodule BlockScoutWeb.ApiRouter do
end
end
+ scope "/polygon-edge" do
+ if System.get_env("CHAIN_TYPE") == "polygon_edge" do
+ get("/deposits", V2.PolygonEdgeController, :deposits)
+ get("/deposits/count", V2.PolygonEdgeController, :deposits_count)
+ get("/withdrawals", V2.PolygonEdgeController, :withdrawals)
+ get("/withdrawals/count", V2.PolygonEdgeController, :withdrawals_count)
+ end
+ end
+
scope "/withdrawals" do
get("/", V2.WithdrawalController, :withdrawals_list)
get("/counters", V2.WithdrawalController, :withdrawals_counters)
end
+
+ scope "/zkevm" do
+ if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do
+ get("/batches", V2.ZkevmController, :batches)
+ get("/batches/count", V2.ZkevmController, :batches_count)
+ get("/batches/:batch_number", V2.ZkevmController, :batch)
+ end
+ end
+
+ scope "/proxy" do
+ scope "/noves-fi" do
+ get("/transactions/:transaction_hash_param", V2.Proxy.NovesFiController, :transaction)
+ get("/transactions/:transaction_hash_param/describe", V2.Proxy.NovesFiController, :describe_transaction)
+ get("/addresses/:address_hash_param/transactions", V2.Proxy.NovesFiController, :address_transactions)
+ end
+ end
end
scope "/v1", as: :api_v1 do
pipe_through(:api)
alias BlockScoutWeb.API.{EthRPC, RPC, V1}
- alias BlockScoutWeb.API.V1.HealthController
+ alias BlockScoutWeb.API.V1.{GasPriceOracleController, HealthController}
alias BlockScoutWeb.API.V2.SearchController
# leave the same endpoint in v1 in order to keep backward compatibility
get("/search", SearchController, :search)
- get("/health", HealthController, :health)
- get("/gas-price-oracle", V1.GasPriceOracleController, :gas_price_oracle)
+
+ @max_complexity 200
+
+ forward("/graphql", Absinthe.Plug,
+ schema: BlockScoutWeb.Schema,
+ analyze_complexity: true,
+ max_complexity: @max_complexity
+ )
+
+ get("/transactions-csv", AddressTransactionController, :transactions_csv)
+
+ get("/token-transfers-csv", AddressTransactionController, :token_transfers_csv)
+
+ get("/internal-transactions-csv", AddressTransactionController, :internal_transactions_csv)
+
+ get("/logs-csv", AddressTransactionController, :logs_csv)
+
+ scope "/health" do
+ get("/", HealthController, :health)
+ get("/liveness", HealthController, :liveness)
+ get("/readiness", HealthController, :readiness)
+ end
+
+ get("/gas-price-oracle", GasPriceOracleController, :gas_price_oracle)
if Application.compile_env(:block_scout_web, __MODULE__)[:reading_enabled] do
get("/supply", V1.SupplyController, :supply)
diff --git a/apps/block_scout_web/lib/block_scout_web/application.ex b/apps/block_scout_web/lib/block_scout_web/application.ex
index 2d84cc8b101b..f323ef8e959d 100644
--- a/apps/block_scout_web/lib/block_scout_web/application.ex
+++ b/apps/block_scout_web/lib/block_scout_web/application.ex
@@ -8,7 +8,7 @@ defmodule BlockScoutWeb.Application do
alias BlockScoutWeb.API.APILogger
alias BlockScoutWeb.Counters.{BlocksIndexedCounter, InternalTransactionsIndexedCounter}
alias BlockScoutWeb.{Endpoint, Prometheus}
- alias BlockScoutWeb.RealtimeEventHandler
+ alias BlockScoutWeb.{MainPageRealtimeEventHandler, RealtimeEventHandler, SmartContractRealtimeEventHandler}
def start(_type, _args) do
import Supervisor
@@ -34,7 +34,9 @@ defmodule BlockScoutWeb.Application do
{Phoenix.PubSub, name: BlockScoutWeb.PubSub},
child_spec(Endpoint, []),
{Absinthe.Subscription, Endpoint},
+ {MainPageRealtimeEventHandler, name: MainPageRealtimeEventHandler},
{RealtimeEventHandler, name: RealtimeEventHandler},
+ {SmartContractRealtimeEventHandler, name: SmartContractRealtimeEventHandler},
{BlocksIndexedCounter, name: BlocksIndexedCounter},
{InternalTransactionsIndexedCounter, name: InternalTransactionsIndexedCounter}
]
diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex
index cc83b040a75e..8bb9d4d0eb2c 100644
--- a/apps/block_scout_web/lib/block_scout_web/chain.ex
+++ b/apps/block_scout_web/lib/block_scout_web/chain.ex
@@ -5,7 +5,6 @@ defmodule BlockScoutWeb.Chain do
import Explorer.Chain,
only: [
- balance_in_fiat: 2,
find_or_insert_address_from_hash: 1,
hash_to_block: 1,
hash_to_transaction: 1,
@@ -16,6 +15,10 @@ defmodule BlockScoutWeb.Chain do
token_contract_address_from_token_name: 1
]
+ import Explorer.Helper, only: [parse_integer: 1]
+
+ alias Ecto.Association.NotLoaded
+ alias Explorer.Account.{TagAddress, TagTransaction, WatchlistAddress}
alias Explorer.Chain.Block.Reward
alias Explorer.Chain.{
@@ -23,6 +26,7 @@ defmodule BlockScoutWeb.Chain do
Address.CoinBalance,
Address.CurrentTokenBalance,
Block,
+ Hash,
InternalTransaction,
Log,
SmartContract,
@@ -30,10 +34,12 @@ defmodule BlockScoutWeb.Chain do
Token.Instance,
TokenTransfer,
Transaction,
+ Transaction.StateChange,
Wei,
Withdrawal
}
+ alias Explorer.Chain.Zkevm.TransactionBatch
alias Explorer.PagingOptions
defimpl Poison.Encoder, for: Decimal do
@@ -96,15 +102,20 @@ defmodule BlockScoutWeb.Chain do
end
end
- def next_page_params([], _list, _params), do: nil
+ @spec next_page_params(any, list(), map(), (any -> map())) :: nil | map
+ def next_page_params(next_page, list, params, paging_function \\ &paging_params/1)
+
+ def next_page_params([], _list, _params, _), do: nil
+
+ def next_page_params(_, list, params, paging_function) do
+ paging_params = paging_function.(List.last(list))
- def next_page_params(_, list, params) do
- next_page_params = Map.merge(params, paging_params(List.last(list)))
- current_items_count_str = Map.get(next_page_params, "items_count")
+ next_page_params = Map.merge(params, paging_params)
+ current_items_count_string = Map.get(next_page_params, "items_count")
items_count =
- if current_items_count_str do
- {current_items_count, _} = Integer.parse(current_items_count_str)
+ if is_binary(current_items_count_string) do
+ {current_items_count, _} = Integer.parse(current_items_count_string)
current_items_count + Enum.count(list)
else
Enum.count(list)
@@ -113,9 +124,16 @@ defmodule BlockScoutWeb.Chain do
Map.put(next_page_params, "items_count", items_count)
end
- def paging_options(%{"hash" => hash, "fetched_coin_balance" => fetched_coin_balance}) do
- with {coin_balance, ""} <- Integer.parse(fetched_coin_balance),
- {:ok, address_hash} <- string_to_address_hash(hash) do
+ @doc """
+ Makes Explorer.PagingOptions map. Overloaded by different params in the input map
+ for different modules using this function.
+ """
+ @spec paging_options(any) ::
+ [{:paging_options, Explorer.PagingOptions.t()}, ...] | Explorer.PagingOptions.t()
+ def paging_options(%{"hash" => hash_string, "fetched_coin_balance" => fetched_coin_balance_string})
+ when is_binary(hash_string) and is_binary(fetched_coin_balance_string) do
+ with {coin_balance, ""} <- Integer.parse(fetched_coin_balance_string),
+ {:ok, address_hash} <- string_to_address_hash(hash_string) do
[paging_options: %{@default_paging_options | key: {%Wei{value: Decimal.new(coin_balance)}, address_hash}}]
else
_ ->
@@ -124,32 +142,87 @@ defmodule BlockScoutWeb.Chain do
end
def paging_options(%{
- "address_hash" => address_hash,
- "tx_hash" => tx_hash,
- "block_hash" => block_hash,
- "holder_count" => holder_count,
- "name" => name,
- "inserted_at" => inserted_at,
- "item_type" => item_type
+ "fee" => fee_string,
+ "value" => value_string,
+ "block_number" => block_number_string,
+ "index" => index_string,
+ "inserted_at" => inserted_at_string,
+ "hash" => hash_string
}) do
+ with {:ok, hash} <- string_to_transaction_hash(hash_string),
+ {:ok, inserted_at, _} <- DateTime.from_iso8601(inserted_at_string) do
+ [
+ paging_options: %{
+ @default_paging_options
+ | key: %{
+ fee: decimal_parse(fee_string),
+ value: decimal_parse(value_string),
+ block_number: parse_integer(block_number_string),
+ index: parse_integer(index_string),
+ inserted_at: inserted_at,
+ hash: hash
+ }
+ }
+ ]
+ else
+ _ -> [paging_options: @default_paging_options]
+ end
+ end
+
+ def paging_options(%{
+ "address_hash" => address_hash_string,
+ "tx_hash" => tx_hash_string,
+ "block_hash" => block_hash_string,
+ "holder_count" => holder_count_string,
+ "name" => name_string,
+ "inserted_at" => inserted_at_string,
+ "item_type" => item_type_string
+ })
+ when is_binary(address_hash_string) and is_binary(tx_hash_string) and is_binary(block_hash_string) and
+ is_binary(holder_count_string) and is_binary(name_string) and is_binary(inserted_at_string) and
+ is_binary(item_type_string) do
[
paging_options: %{
@default_paging_options
- | key: {address_hash, tx_hash, block_hash, holder_count, name, inserted_at, item_type}
+ | key:
+ {address_hash_string, tx_hash_string, block_hash_string, holder_count_string, name_string,
+ inserted_at_string, item_type_string}
}
]
end
- def paging_options(%{"market_cap" => market_cap, "holder_count" => holder_count, "name" => token_name}) do
- market_cap_decimal =
- case Decimal.parse(market_cap) do
- {decimal, ""} -> Decimal.round(decimal, 16)
- _ -> nil
- end
-
- case Integer.parse(holder_count) do
- {holder_count, ""} ->
- [paging_options: %{@default_paging_options | key: {market_cap_decimal, holder_count, token_name}}]
+ def paging_options(
+ %{
+ "market_cap" => market_cap_string,
+ "holder_count" => holder_count_string,
+ "name" => name_string,
+ "contract_address_hash" => contract_address_hash_string,
+ "is_name_null" => is_name_null_string
+ } = params
+ )
+ when is_binary(market_cap_string) and is_binary(holder_count_string) and is_binary(name_string) and
+ is_binary(contract_address_hash_string) and is_binary(is_name_null_string) do
+ market_cap_decimal = decimal_parse(market_cap_string)
+
+ fiat_value_decimal = decimal_parse(params["fiat_value"])
+
+ holder_count = parse_integer(holder_count_string)
+ token_name = if is_name_null_string == "true", do: nil, else: name_string
+
+ case Hash.Address.cast(contract_address_hash_string) do
+ {:ok, contract_address_hash} ->
+ [
+ paging_options: %{
+ @default_paging_options
+ | key: %{
+ fiat_value: fiat_value_decimal,
+ circulating_market_cap: market_cap_decimal,
+ holder_count: holder_count,
+ name: token_name,
+ contract_address_hash: contract_address_hash
+ }
+ }
+ ]
_ ->
[paging_options: @default_paging_options]
@@ -160,7 +233,8 @@ defmodule BlockScoutWeb.Chain do
"block_number" => block_number_string,
"transaction_index" => transaction_index_string,
"index" => index_string
- }) do
+ })
+ when is_binary(block_number_string) and is_binary(transaction_index_string) and is_binary(index_string) do
with {block_number, ""} <- Integer.parse(block_number_string),
{transaction_index, ""} <- Integer.parse(transaction_index_string),
{index, ""} <- Integer.parse(index_string) do
@@ -178,7 +252,10 @@ defmodule BlockScoutWeb.Chain do
"batch_block_hash" => batch_block_hash_string,
"batch_transaction_hash" => batch_transaction_hash_string,
"index_in_batch" => index_in_batch_string
- }) do
+ })
+ when is_binary(block_number_string) and is_binary(index_string) and is_binary(batch_log_index_string) and
+ is_binary(batch_transaction_hash_string) and is_binary(index_in_batch_string) and
+ is_binary(index_in_batch_string) do
with {block_number, ""} <- Integer.parse(block_number_string),
{index, ""} <- Integer.parse(index_string),
{index_in_batch, ""} <- Integer.parse(index_in_batch_string),
@@ -203,7 +280,9 @@ defmodule BlockScoutWeb.Chain do
"batch_block_hash" => batch_block_hash_string,
"batch_transaction_hash" => batch_transaction_hash_string,
"index_in_batch" => index_in_batch_string
- }) do
+ })
+ when is_binary(batch_log_index_string) and is_binary(batch_block_hash_string) and
+ is_binary(batch_transaction_hash_string) and is_binary(index_in_batch_string) do
with {index_in_batch, ""} <- Integer.parse(index_in_batch_string),
{:ok, batch_transaction_hash} <- string_to_transaction_hash(batch_transaction_hash_string),
{:ok, batch_block_hash} <- string_to_block_hash(batch_block_hash_string),
@@ -220,7 +299,8 @@ defmodule BlockScoutWeb.Chain do
end
end
- def paging_options(%{"block_number" => block_number_string, "index" => index_string}) do
+ def paging_options(%{"block_number" => block_number_string, "index" => index_string})
+ when is_binary(block_number_string) and is_binary(index_string) do
with {block_number, ""} <- Integer.parse(block_number_string),
{index, ""} <- Integer.parse(index_string) do
[paging_options: %{@default_paging_options | key: {block_number, index}}]
@@ -230,7 +310,7 @@ defmodule BlockScoutWeb.Chain do
end
end
- def paging_options(%{"block_number" => block_number_string}) do
+ def paging_options(%{"block_number" => block_number_string}) when is_binary(block_number_string) do
case Integer.parse(block_number_string) do
{block_number, ""} ->
[paging_options: %{@default_paging_options | key: {block_number}}]
@@ -254,7 +334,22 @@ defmodule BlockScoutWeb.Chain do
[paging_options: %{@default_paging_options | key: {index}}]
end
- def paging_options(%{"inserted_at" => inserted_at_string, "hash" => hash_string}) do
+ def paging_options(%{"number" => number_string}) when is_binary(number_string) do
+ case Integer.parse(number_string) do
+ {number, ""} ->
+ [paging_options: %{@default_paging_options | key: {number}}]
+
+ _ ->
+ [paging_options: @default_paging_options]
+ end
+ end
+
+ def paging_options(%{"number" => number}) when is_integer(number) do
+ [paging_options: %{@default_paging_options | key: {number}}]
+ end
+
+ def paging_options(%{"inserted_at" => inserted_at_string, "hash" => hash_string})
+ when is_binary(inserted_at_string) and is_binary(hash_string) do
with {:ok, inserted_at, _} <- DateTime.from_iso8601(inserted_at_string),
{:ok, hash} <- string_to_transaction_hash(hash_string) do
[paging_options: %{@default_paging_options | key: {inserted_at, hash}, is_pending_tx: true}]
@@ -271,11 +366,12 @@ defmodule BlockScoutWeb.Chain do
[paging_options: %{@default_paging_options | key: {value, address_hash}}]
end
- def paging_options(%{"fiat_value" => fiat_value_string, "value" => value, "id" => id_string}) do
+ def paging_options(%{"fiat_value" => fiat_value_string, "value" => value_string, "id" => id_string})
+ when is_binary(fiat_value_string) and is_binary(value_string) and is_binary(id_string) do
with {id, ""} <- Integer.parse(id_string),
- {value, ""} <- Decimal.parse(value),
+ {value, ""} <- Decimal.parse(value_string),
{_id, _value, {fiat_value, ""}} <- {id, value, Decimal.parse(fiat_value_string)} do
- [paging_options: %{@default_paging_options | key: {Decimal.round(fiat_value, 16), value, id}}]
+ [paging_options: %{@default_paging_options | key: {fiat_value, value, id}}]
else
{id, value, :error} ->
[paging_options: %{@default_paging_options | key: {nil, value, id}}]
@@ -285,10 +381,54 @@ defmodule BlockScoutWeb.Chain do
end
end
- def paging_options(%{"smart_contract_id" => id}) do
+ def paging_options(%{"smart_contract_id" => id_str} = params) do
+ transactions_count = parse_integer(params["tx_count"])
+ coin_balance = parse_integer(params["coin_balance"])
+ id = parse_integer(id_str)
+
+ [
+ paging_options: %{
+ @default_paging_options
+ | key: %{id: id, transactions_count: transactions_count, fetched_coin_balance: coin_balance}
+ }
+ ]
+ end
+
+ def paging_options(%{"items_count" => items_count_string, "state_changes" => _}) when is_binary(items_count_string) do
+ case Integer.parse(items_count_string) do
+ {count, ""} -> [paging_options: %{@default_paging_options | key: {count}}]
+ _ -> @default_paging_options
+ end
+ end
+
+ # clause for Polygon Edge Deposits and Withdrawals and for account's entities pagination
+ def paging_options(%{"id" => id_string}) when is_binary(id_string) do
+ case Integer.parse(id_string) do
+ {id, ""} ->
+ [paging_options: %{@default_paging_options | key: {id}}]
+
+ _ ->
+ [paging_options: @default_paging_options]
+ end
+ end
+
+ # clause for Polygon Edge Deposits and Withdrawals and for account's entities pagination
+ def paging_options(%{"id" => id}) when is_integer(id) do
[paging_options: %{@default_paging_options | key: {id}}]
end
+ def paging_options(%{
+ "token_contract_address_hash" => token_contract_address_hash,
+ "token_id" => token_id,
+ "token_type" => token_type
+ }) do
+ [paging_options: %{@default_paging_options | key: {token_contract_address_hash, token_id, token_type}}]
+ end
+
+ def paging_options(%{"token_contract_address_hash" => token_contract_address_hash, "token_type" => token_type}) do
+ [paging_options: %{@default_paging_options | key: {token_contract_address_hash, token_type}}]
+ end
+
def paging_options(_params), do: [paging_options: @default_paging_options]
def put_key_value_to_paging_options([paging_options: paging_options], key, value) do
@@ -347,6 +487,13 @@ defmodule BlockScoutWeb.Chain do
def split_list_by_page(list_plus_one), do: Enum.split(list_plus_one, @page_size)
+ defp decimal_parse(input_string) do
+ case Decimal.parse(input_string) do
+ {decimal, ""} -> decimal
+ _ -> nil
+ end
+ end
+
defp address_from_param(param) do
case string_to_address_hash(param) do
{:ok, hash} ->
@@ -369,18 +516,36 @@ defmodule BlockScoutWeb.Chain do
end
defp paging_params(%Token{
+ contract_address_hash: contract_address_hash,
circulating_market_cap: circulating_market_cap,
holder_count: holder_count,
- name: token_name
+ name: token_name,
+ fiat_value: fiat_value
}) do
- %{"market_cap" => circulating_market_cap, "holder_count" => holder_count, "name" => token_name}
+ %{
+ "market_cap" => circulating_market_cap,
+ "holder_count" => holder_count,
+ "contract_address_hash" => contract_address_hash,
+ "name" => token_name,
+ "is_name_null" => is_nil(token_name),
+ "fiat_value" => fiat_value
+ }
+ end
+
+ defp paging_params(%TagAddress{id: id}) do
+ %{"id" => id}
+ end
+
+ defp paging_params(%TagTransaction{id: id}) do
+ %{"id" => id}
end
- defp paging_params([
- %Token{circulating_market_cap: circulating_market_cap, holder_count: holder_count, name: token_name},
- _
- ]) do
- %{"market_cap" => circulating_market_cap, "holder_count" => holder_count, "name" => token_name}
+ defp paging_params(%WatchlistAddress{id: id}) do
+ %{"id" => id}
+ end
+
+ defp paging_params([%Token{} = token, _]) do
+ paging_params(token)
end
defp paging_params({%Reward{block: %{number: number}}, _}) do
@@ -396,12 +561,8 @@ defmodule BlockScoutWeb.Chain do
%{"block_number" => block_number, "transaction_index" => transaction_index, "index" => index}
end
- defp paging_params(%Log{index: index} = log) do
- if Ecto.assoc_loaded?(log.transaction) do
- %{"block_number" => log.transaction.block_number, "transaction_index" => log.transaction.index, "index" => index}
- else
- %{"index" => index}
- end
+ defp paging_params(%Log{index: index, block_number: block_number}) do
+ %{"block_number" => block_number, "index" => index}
end
defp paging_params(%Transaction{block_number: nil, inserted_at: inserted_at, hash: hash}) do
@@ -426,22 +587,32 @@ defmodule BlockScoutWeb.Chain do
%{"address_hash" => to_string(address_hash), "value" => Decimal.to_integer(value)}
end
- defp paging_params({%CurrentTokenBalance{id: id, value: value} = ctb, token}) do
- %{"fiat_value" => balance_in_fiat(ctb, token), "value" => value, "id" => id}
- end
-
defp paging_params(%CoinBalance{block_number: block_number}) do
%{"block_number" => block_number}
end
- defp paging_params(%SmartContract{} = smart_contract) do
+ defp paging_params(%SmartContract{address: %NotLoaded{}} = smart_contract) do
%{"smart_contract_id" => smart_contract.id}
end
+ defp paging_params(%SmartContract{} = smart_contract) do
+ %{
+ "smart_contract_id" => smart_contract.id,
+ "tx_count" => smart_contract.address.transactions_count,
+ "coin_balance" =>
+ smart_contract.address.fetched_coin_balance && Wei.to(smart_contract.address.fetched_coin_balance, :wei)
+ }
+ end
+
defp paging_params(%Withdrawal{index: index}) do
%{"index" => index}
end
+ # clause for zkEVM batches pagination
+ defp paging_params(%TransactionBatch{number: number}) do
+ %{"number" => number}
+ end
+
# clause for search results pagination
defp paging_params(%{
address_hash: address_hash,
@@ -469,6 +640,22 @@ defmodule BlockScoutWeb.Chain do
%{"unique_token" => Decimal.to_integer(token_id)}
end
+ defp paging_params(%StateChange{}) do
+ %{"state_changes" => nil}
+ end
+
+ # clause for Polygon Edge Deposits and Withdrawals
+ defp paging_params(%{msg_id: msg_id}) do
+ %{"id" => msg_id}
+ end
+
+ @spec paging_params_with_fiat_value(CurrentTokenBalance.t()) :: %{
+ required(String.t()) => Decimal.t() | non_neg_integer() | nil
+ }
+ def paging_params_with_fiat_value(%CurrentTokenBalance{id: id, value: value} = ctb) do
+ %{"fiat_value" => ctb.fiat_value, "value" => value, "id" => id}
+ end
+
defp block_or_transaction_from_param(param) do
with {:error, :not_found} <- transaction_from_param(param) do
hash_string_to_block(param)
diff --git a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex
index 6072545f34c4..f0ead554c72d 100644
--- a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex
+++ b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex
@@ -4,6 +4,8 @@ defmodule BlockScoutWeb.AddressChannel do
"""
use BlockScoutWeb, :channel
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
+
alias BlockScoutWeb.API.V2.AddressView, as: AddressViewAPI
alias BlockScoutWeb.API.V2.SmartContractView, as: SmartContractViewAPI
alias BlockScoutWeb.API.V2.TransactionView, as: TransactionViewAPI
@@ -18,7 +20,6 @@ defmodule BlockScoutWeb.AddressChannel do
alias Explorer.{Chain, Market, Repo}
alias Explorer.Chain.{Hash, Transaction, Wei}
alias Explorer.Chain.Hash.Address, as: AddressHash
- alias Explorer.ExchangeRates.Token
alias Phoenix.View
intercept([
@@ -29,11 +30,13 @@ defmodule BlockScoutWeb.AddressChannel do
"transaction",
"verification_result",
"token_transfer",
- "pending_transaction"
+ "pending_transaction",
+ "address_current_token_balances"
])
- {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
+ {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string())
@burn_address_hash burn_address_hash
+ @current_token_balances_limit 50
def join("addresses:" <> address_hash, _params, socket) do
{:ok, %{}, assign(socket, :address_hash, address_hash)}
@@ -43,7 +46,7 @@ defmodule BlockScoutWeb.AddressChannel do
with {:ok, casted_address_hash} <- AddressHash.cast(socket.assigns.address_hash),
{:ok, address = %{fetched_coin_balance: balance}} when not is_nil(balance) <-
Chain.hash_to_address(casted_address_hash),
- exchange_rate <- Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate <- Market.get_coin_exchange_rate(),
{:ok, rendered} <- render_balance_card(address, exchange_rate, socket) do
reply =
{:ok,
@@ -226,6 +229,34 @@ defmodule BlockScoutWeb.AddressChannel do
def handle_out("pending_transaction", data, socket), do: handle_transaction(data, socket, "transaction")
+ def handle_out(
+ "address_current_token_balances",
+ %{address_current_token_balances: address_current_token_balances},
+ %Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket
+ ) do
+ push_current_token_balances(socket, address_current_token_balances, "erc_20", "ERC-20")
+ push_current_token_balances(socket, address_current_token_balances, "erc_721", "ERC-721")
+ push_current_token_balances(socket, address_current_token_balances, "erc_1155", "ERC-1155")
+
+ {:noreply, socket}
+ end
+
+ def handle_out("address_current_token_balances", _, socket) do
+ {:noreply, socket}
+ end
+
+ defp push_current_token_balances(socket, address_current_token_balances, event_postfix, token_type) do
+ filtered_ctbs = address_current_token_balances |> Enum.filter(fn ctb -> ctb.token_type == token_type end)
+
+ push(socket, "updated_token_balances_" <> event_postfix, %{
+ token_balances:
+ AddressViewAPI.render("token_balances.json", %{
+ token_balances: Enum.take(filtered_ctbs, @current_token_balances_limit)
+ }),
+ overflow: Enum.count(filtered_ctbs) > @current_token_balances_limit
+ })
+ end
+
def push_current_coin_balance(
%Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket,
block_number,
@@ -233,7 +264,7 @@ defmodule BlockScoutWeb.AddressChannel do
) do
push(socket, "current_coin_balance", %{
coin_balance: (coin_balance && coin_balance.value) || %Wei{value: Decimal.new(0)},
- exchange_rate: (Market.get_exchange_rate(Explorer.coin()) || Token.null()).usd_value,
+ exchange_rate: Market.get_coin_exchange_rate().usd_value,
block_number: block_number
})
end
@@ -248,7 +279,7 @@ defmodule BlockScoutWeb.AddressChannel do
conn: socket,
address: Chain.hash_to_address(hash),
coin_balance: (coin_balance && coin_balance.value) || %Wei{value: Decimal.new(0)},
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null()
+ exchange_rate: Market.get_coin_exchange_rate()
)
rendered_link =
diff --git a/apps/block_scout_web/lib/block_scout_web/channels/token_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/token_channel.ex
index ac4295e6b010..0de43f328859 100644
--- a/apps/block_scout_web/lib/block_scout_web/channels/token_channel.ex
+++ b/apps/block_scout_web/lib/block_scout_web/channels/token_channel.ex
@@ -4,6 +4,8 @@ defmodule BlockScoutWeb.TokenChannel do
"""
use BlockScoutWeb, :channel
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
+
alias BlockScoutWeb.{CurrencyHelper, TokensView}
alias BlockScoutWeb.Tokens.TransferView
alias Explorer.Chain
@@ -12,7 +14,7 @@ defmodule BlockScoutWeb.TokenChannel do
intercept(["token_transfer", "token_total_supply"])
- {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
+ {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string())
@burn_address_hash burn_address_hash
def join("tokens:" <> _transaction_hash, _params, socket) do
diff --git a/apps/block_scout_web/lib/block_scout_web/channels/transaction_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/transaction_channel.ex
index df026d0b0b58..5c1247786a05 100644
--- a/apps/block_scout_web/lib/block_scout_web/channels/transaction_channel.ex
+++ b/apps/block_scout_web/lib/block_scout_web/channels/transaction_channel.ex
@@ -4,6 +4,8 @@ defmodule BlockScoutWeb.TransactionChannel do
"""
use BlockScoutWeb, :channel
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
+
alias BlockScoutWeb.API.V2.TransactionView, as: TransactionViewV2
alias BlockScoutWeb.{TransactionRawTraceView, TransactionView}
alias Explorer.Chain
@@ -12,7 +14,7 @@ defmodule BlockScoutWeb.TransactionChannel do
intercept(["pending_transaction", "transaction", "raw_trace"])
- {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
+ {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string())
@burn_address_hash burn_address_hash
def join("transactions:new_transaction", _params, socket) do
diff --git a/apps/block_scout_web/lib/block_scout_web/channels/user_socket_v2.ex b/apps/block_scout_web/lib/block_scout_web/channels/user_socket_v2.ex
index b012b8db2a9f..159993433e35 100644
--- a/apps/block_scout_web/lib/block_scout_web/channels/user_socket_v2.ex
+++ b/apps/block_scout_web/lib/block_scout_web/channels/user_socket_v2.ex
@@ -10,6 +10,7 @@ defmodule BlockScoutWeb.UserSocketV2 do
channel("rewards:*", BlockScoutWeb.RewardChannel)
channel("transactions:*", BlockScoutWeb.TransactionChannel)
channel("tokens:*", BlockScoutWeb.TokenChannel)
+ channel("zkevm_batches:*", BlockScoutWeb.ZkevmConfirmedBatchChannel)
def connect(_params, socket) do
{:ok, socket}
diff --git a/apps/block_scout_web/lib/block_scout_web/channels/zkevm_confirmed_batch_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/zkevm_confirmed_batch_channel.ex
new file mode 100644
index 000000000000..9007f1176412
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/channels/zkevm_confirmed_batch_channel.ex
@@ -0,0 +1,28 @@
+defmodule BlockScoutWeb.ZkevmConfirmedBatchChannel do
+ @moduledoc """
+ Establishes pub/sub channel for live updates of zkEVM confirmed batch events.
+ """
+ use BlockScoutWeb, :channel
+
+ alias BlockScoutWeb.API.V2.ZkevmView
+
+ intercept(["new_zkevm_confirmed_batch"])
+
+ def join("zkevm_batches:new_zkevm_confirmed_batch", _params, socket) do
+ {:ok, %{}, socket}
+ end
+
+ def handle_out(
+ "new_zkevm_confirmed_batch",
+ %{batch: batch},
+ %Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket
+ ) do
+ rendered_batch = ZkevmView.render("zkevm_batch.json", %{batch: batch, socket: nil})
+
+ push(socket, "new_zkevm_confirmed_batch", %{
+ batch: rendered_batch
+ })
+
+ {:noreply, socket}
+ end
+end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/email_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/email_controller.ex
index b8f2ce763166..9e79c2d33645 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/email_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/email_controller.ex
@@ -8,10 +8,14 @@ defmodule BlockScoutWeb.Account.Api.V1.EmailController do
require Logger
+ @invalid_session_key Application.compile_env(:block_scout_web, :invalid_session_key)
+
action_fallback(BlockScoutWeb.Account.Api.V1.FallbackController)
+ plug(:fetch_cookies, signed: [@invalid_session_key])
+
def resend_email(conn, _params) do
- with user <- get_session(conn, :current_user),
+ with user <- conn.cookies[@invalid_session_key],
{:auth, false} <- {:auth, is_nil(user)},
{:email_verified, false} <- {:email_verified, user[:email_verified]},
{:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(user[:id])},
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex
index 42b67c7c1759..724378cc69d6 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex
@@ -2,19 +2,29 @@ defmodule BlockScoutWeb.Account.Api.V1.UserController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
+
+ import BlockScoutWeb.Chain,
+ only: [
+ next_page_params: 3,
+ paging_options: 1,
+ split_list_by_page: 1
+ ]
+
+ import BlockScoutWeb.PagingHelper, only: [delete_parameters_from_next_page_params: 1]
+
import Ecto.Query, only: [from: 2]
alias BlockScoutWeb.Models.UserFromAuth
alias Explorer.Account.Api.Key, as: ApiKey
alias Explorer.Account.CustomABI
alias Explorer.Account.{Identity, PublicTagsRequest, TagAddress, TagTransaction, WatchlistAddress}
- alias Explorer.ExchangeRates.Token
- alias Explorer.{Market, Repo}
+ alias Explorer.{Chain, Market, PagingOptions, Repo}
alias Plug.CSRFProtection
action_fallback(BlockScoutWeb.Account.Api.V1.FallbackController)
@ok_message "OK"
+ @token_balances_amount 150
def info(conn, _params) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
@@ -25,17 +35,85 @@ defmodule BlockScoutWeb.Account.Api.V1.UserController do
end
end
- def watchlist(conn, _params) do
+ def watchlist_old(conn, _params) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)},
{:watchlist, %{watchlists: [watchlist | _]}} <-
{:watchlist, Repo.account_repo().preload(identity, :watchlists)},
watchlist_with_addresses <- preload_watchlist_addresses(watchlist) do
+ watchlist_addresses =
+ Enum.map(watchlist_with_addresses.watchlist_addresses, fn wa ->
+ balances =
+ Chain.fetch_paginated_last_token_balances(wa.address_hash,
+ paging_options: %PagingOptions{page_size: @token_balances_amount + 1}
+ )
+
+ count = Enum.count(balances)
+ overflow? = count > @token_balances_amount
+
+ fiat_sum =
+ balances
+ |> Enum.take(@token_balances_amount)
+ |> Enum.reduce(Decimal.new(0), fn tb, acc -> Decimal.add(acc, tb.fiat_value || 0) end)
+
+ %WatchlistAddress{
+ wa
+ | tokens_fiat_value: fiat_sum,
+ tokens_count: min(count, @token_balances_amount),
+ tokens_overflow: overflow?
+ }
+ end)
+
conn
|> put_status(200)
|> render(:watchlist_addresses, %{
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
- watchlist_addresses: watchlist_with_addresses.watchlist_addresses
+ exchange_rate: Market.get_coin_exchange_rate(),
+ watchlist_addresses: watchlist_addresses
+ })
+ end
+ end
+
+ def watchlist(conn, params) do
+ with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
+ {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)},
+ {:watchlist, %{watchlists: [watchlist | _]}} <-
+ {:watchlist, Repo.account_repo().preload(identity, :watchlists)} do
+ results_plus_one = WatchlistAddress.get_watchlist_addresses_by_watchlist_id(watchlist.id, paging_options(params))
+
+ {watchlist_addresses, next_page} = split_list_by_page(results_plus_one)
+
+ next_page_params =
+ next_page |> next_page_params(watchlist_addresses, delete_parameters_from_next_page_params(params))
+
+ watchlist_addresses_prepared =
+ Enum.map(watchlist_addresses, fn wa ->
+ balances =
+ Chain.fetch_paginated_last_token_balances(wa.address_hash,
+ paging_options: %PagingOptions{page_size: @token_balances_amount + 1}
+ )
+
+ count = Enum.count(balances)
+ overflow? = count > @token_balances_amount
+
+ fiat_sum =
+ balances
+ |> Enum.take(@token_balances_amount)
+ |> Enum.reduce(Decimal.new(0), fn tb, acc -> Decimal.add(acc, tb.fiat_value || 0) end)
+
+ %WatchlistAddress{
+ wa
+ | tokens_fiat_value: fiat_sum,
+ tokens_count: min(count, @token_balances_amount),
+ tokens_overflow: overflow?
+ }
+ end)
+
+ conn
+ |> put_status(200)
+ |> render(:watchlist_addresses, %{
+ exchange_rate: Market.get_coin_exchange_rate(),
+ watchlist_addresses: watchlist_addresses_prepared,
+ next_page_params: next_page_params
})
end
end
@@ -103,7 +181,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserController do
|> put_status(200)
|> render(:watchlist_address, %{
watchlist_address: watchlist_address,
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null()
+ exchange_rate: Market.get_coin_exchange_rate()
})
end
end
@@ -160,12 +238,12 @@ defmodule BlockScoutWeb.Account.Api.V1.UserController do
|> put_status(200)
|> render(:watchlist_address, %{
watchlist_address: watchlist_address,
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null()
+ exchange_rate: Market.get_coin_exchange_rate()
})
end
end
- def tags_address(conn, _params) do
+ def tags_address_old(conn, _params) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)},
address_tags <- TagAddress.get_tags_address_by_identity_id(identity.id) do
@@ -175,6 +253,21 @@ defmodule BlockScoutWeb.Account.Api.V1.UserController do
end
end
+ def tags_address(conn, params) do
+ with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
+ {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)} do
+ results_plus_one = TagAddress.get_tags_address_by_identity_id(identity.id, paging_options(params))
+
+ {tags, next_page} = split_list_by_page(results_plus_one)
+
+ next_page_params = next_page |> next_page_params(tags, delete_parameters_from_next_page_params(params))
+
+ conn
+ |> put_status(200)
+ |> render(:address_tags, %{address_tags: tags, next_page_params: next_page_params})
+ end
+ end
+
def delete_tag_address(conn, %{"id" => tag_id}) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)},
@@ -219,7 +312,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserController do
end
end
- def tags_transaction(conn, _params) do
+ def tags_transaction_old(conn, _params) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)},
transaction_tags <- TagTransaction.get_tags_transaction_by_identity_id(identity.id) do
@@ -229,6 +322,21 @@ defmodule BlockScoutWeb.Account.Api.V1.UserController do
end
end
+ def tags_transaction(conn, params) do
+ with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
+ {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)} do
+ results_plus_one = TagTransaction.get_tags_transaction_by_identity_id(identity.id, paging_options(params))
+
+ {tags, next_page} = split_list_by_page(results_plus_one)
+
+ next_page_params = next_page |> next_page_params(tags, delete_parameters_from_next_page_params(params))
+
+ conn
+ |> put_status(200)
+ |> render(:transaction_tags, %{transaction_tags: tags, next_page_params: next_page_params})
+ end
+ end
+
def delete_tag_transaction(conn, %{"id" => tag_id}) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)},
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/auth_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/auth_controller.ex
index 6d6c926c2c58..65f29163179b 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/account/auth_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/auth_controller.ex
@@ -38,6 +38,12 @@ defmodule BlockScoutWeb.Account.AuthController do
{:ok, %{email_verified: false} = user} ->
conn
|> put_session(:current_user, user)
+ |> put_resp_cookie(Application.get_env(:block_scout_web, :invalid_session_key), user,
+ max_age: Application.get_env(:block_scout_web, :session_cookie_ttl),
+ sign: true,
+ same_site: "Lax",
+ domain: Application.get_env(:block_scout_web, :cookie_domain)
+ )
|> redirect(to: root())
{:ok, user} ->
@@ -45,6 +51,7 @@ defmodule BlockScoutWeb.Account.AuthController do
conn
|> put_session(:current_user, user)
+ |> delete_resp_cookie(Application.get_env(:block_scout_web, :invalid_session_key))
|> redirect(to: redirect_path(params["path"]))
{:error, reason} ->
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex
index cd85a4c71626..c0bbf0097584 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex
@@ -12,17 +12,16 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
alias BlockScoutWeb.{AccessHelper, AddressCoinBalanceView, Controller}
alias Explorer.{Chain, Market}
alias Explorer.Chain.{Address, Wei}
- alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
- :ok <- Chain.check_address_exists(address_hash),
+ {:ok, address} <- Chain.hash_to_address(address_hash, [], false),
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params) do
full_options = paging_options(params)
- coin_balances_plus_one = Chain.address_to_coin_balances(address_hash, full_options)
+ coin_balances_plus_one = Chain.address_to_coin_balances(address, full_options)
{coin_balances, next_page} = split_list_by_page(coin_balances_plus_one)
@@ -55,7 +54,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
{:restricted_access, _} ->
not_found(conn)
- :not_found ->
+ {:error, :not_found} ->
case Chain.Hash.Address.validate(address_hash_string) do
{:ok, _} ->
json(conn, %{items: [], next_page_path: ""})
@@ -76,7 +75,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
render(conn, "index.html",
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
current_path: Controller.current_full_path(conn),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}),
tags: get_address_tags(address_hash, current_user(conn))
@@ -102,7 +101,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
"index.html",
address: address,
coin_balance_status: nil,
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}),
current_path: Controller.current_full_path(conn),
tags: get_address_tags(address_hash, current_user(conn))
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex
index da6e864e6937..445b7ca48679 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex
@@ -7,7 +7,6 @@ defmodule BlockScoutWeb.AddressContractController do
alias BlockScoutWeb.AccessHelper
alias Explorer.{Chain, Market}
- alias Explorer.ExchangeRates.Token
alias Explorer.SmartContract.Solidity.PublishHelper
alias Indexer.Fetcher.CoinBalanceOnDemand
@@ -24,14 +23,14 @@ defmodule BlockScoutWeb.AddressContractController do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- _ <- PublishHelper.check_and_verify(address_hash_string),
+ _ <- PublishHelper.sourcify_check(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
render(
conn,
"index.html",
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
tags: get_address_tags(address_hash, current_user(conn))
)
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex
index 9f2d689093fc..60f4720aef37 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex
@@ -2,7 +2,6 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.Controller
- alias Explorer.Chain
alias Explorer.Chain.Events.Publisher, as: EventsPublisher
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler}
@@ -12,7 +11,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
alias Explorer.ThirdPartyIntegrations.Sourcify
def new(conn, %{"address_id" => address_hash_string}) do
- if Chain.smart_contract_fully_verified?(address_hash_string) do
+ if SmartContract.verified_with_full_match?(address_hash_string) do
address_contract_path =
conn
|> address_contract_path(:index, address_hash_string)
@@ -38,7 +37,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
render(conn, "new.html",
changeset: changeset,
compiler_versions: compiler_versions,
- evm_versions: CodeCompiler.allowed_evm_versions(),
+ evm_versions: CodeCompiler.evm_versions(:solidity),
address_hash: address_hash_string
)
end
@@ -122,7 +121,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
json_file = PublishHelper.get_one_json(files_array)
if json_file do
- if Chain.smart_contract_fully_verified?(address_hash_string) do
+ if SmartContract.verified_with_full_match?(address_hash_string) do
EventsPublisher.broadcast(
PublishHelper.prepare_verification_error(
"This contract already verified in Blockscout.",
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex
index 99017a86eebd..72cd3aeb3bca 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex
@@ -2,12 +2,11 @@ defmodule BlockScoutWeb.AddressContractVerificationViaFlattenedCodeController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.Controller
- alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler, Solidity.PublisherWorker}
def new(conn, %{"address_id" => address_hash_string}) do
- if Chain.smart_contract_fully_verified?(address_hash_string) do
+ if SmartContract.verified_with_full_match?(address_hash_string) do
address_contract_path =
conn
|> address_contract_path(:index, address_hash_string)
@@ -33,7 +32,7 @@ defmodule BlockScoutWeb.AddressContractVerificationViaFlattenedCodeController do
render(conn, "new.html",
changeset: changeset,
compiler_versions: compiler_versions,
- evm_versions: CodeCompiler.allowed_evm_versions(),
+ evm_versions: CodeCompiler.evm_versions(:solidity),
address_hash: address_hash_string
)
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_json_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_json_controller.ex
index 87a75ac7cb7e..b41ae1517fdc 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_json_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_json_controller.ex
@@ -2,7 +2,6 @@ defmodule BlockScoutWeb.AddressContractVerificationViaJsonController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.Controller
- alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.Solidity.PublishHelper
alias Explorer.ThirdPartyIntegrations.Sourcify
@@ -13,7 +12,7 @@ defmodule BlockScoutWeb.AddressContractVerificationViaJsonController do
|> address_contract_path(:index, address_hash_string)
|> Controller.full_path()
- if Chain.smart_contract_fully_verified?(address_hash_string) do
+ if SmartContract.verified_with_full_match?(address_hash_string) do
redirect(conn, to: address_contract_path)
else
case Sourcify.check_by_address(address_hash_string) do
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_multi_part_files_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_multi_part_files_controller.ex
index cdc6d44b32be..78f6ea6b99c4 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_multi_part_files_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_multi_part_files_controller.ex
@@ -2,12 +2,11 @@ defmodule BlockScoutWeb.AddressContractVerificationViaMultiPartFilesController d
use BlockScoutWeb, :controller
alias BlockScoutWeb.Controller
- alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler}
def new(conn, %{"address_id" => address_hash_string}) do
- if Chain.smart_contract_fully_verified?(address_hash_string) do
+ if SmartContract.verified_with_full_match?(address_hash_string) do
address_contract_path =
conn
|> address_contract_path(:index, address_hash_string)
@@ -33,7 +32,7 @@ defmodule BlockScoutWeb.AddressContractVerificationViaMultiPartFilesController d
render(conn, "new.html",
changeset: changeset,
address_hash: address_hash_string,
- evm_versions: CodeCompiler.allowed_evm_versions(),
+ evm_versions: CodeCompiler.evm_versions(:solidity),
compiler_versions: compiler_versions
)
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_standard_json_input_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_standard_json_input_controller.ex
index 1938269f79ef..37447acd8195 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_standard_json_input_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_standard_json_input_controller.ex
@@ -2,12 +2,11 @@ defmodule BlockScoutWeb.AddressContractVerificationViaStandardJsonInputControlle
use BlockScoutWeb, :controller
alias BlockScoutWeb.Controller
- alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.CompilerVersion
def new(conn, %{"address_id" => address_hash_string}) do
- if Chain.smart_contract_fully_verified?(address_hash_string) do
+ if SmartContract.verified_with_full_match?(address_hash_string) do
address_contract_path =
conn
|> address_contract_path(:index, address_hash_string)
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_vyper_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_vyper_controller.ex
index 19123f940057..6c978901cd0a 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_vyper_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_vyper_controller.ex
@@ -2,12 +2,11 @@ defmodule BlockScoutWeb.AddressContractVerificationVyperController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.Controller
- alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.{CompilerVersion, Vyper.PublisherWorker}
def new(conn, %{"address_id" => address_hash_string}) do
- if Chain.smart_contract_fully_verified?(address_hash_string) do
+ if SmartContract.verified_with_full_match?(address_hash_string) do
address_contract_path =
conn
|> address_contract_path(:index, address_hash_string)
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex
index 4331bdf6befe..3e50dff92ba7 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex
@@ -15,8 +15,8 @@ defmodule BlockScoutWeb.AddressController do
}
alias Explorer.{Chain, Market}
- alias Explorer.Chain.Wei
- alias Explorer.ExchangeRates.Token
+ alias Explorer.Chain.Address.Counters
+ alias Explorer.Chain.{Address, Wei}
alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View
@@ -24,7 +24,7 @@ defmodule BlockScoutWeb.AddressController do
addresses =
params
|> paging_options()
- |> Chain.list_top_addresses()
+ |> Address.list_top_addresses()
{addresses_page, next_page} = split_list_by_page(addresses)
@@ -41,7 +41,7 @@ defmodule BlockScoutWeb.AddressController do
)
end
- exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null()
+ exchange_rate = Market.get_coin_exchange_rate()
total_supply = Chain.total_supply()
items_count_str = Map.get(params, "items_count")
@@ -83,7 +83,7 @@ defmodule BlockScoutWeb.AddressController do
render(conn, "index.html",
current_path: Controller.current_full_path(conn),
- address_count: Chain.address_estimated_count(),
+ address_count: Counters.address_estimated_count(),
total_supply: total_supply
)
end
@@ -101,7 +101,7 @@ defmodule BlockScoutWeb.AddressController do
"_show_address_transactions.html",
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
filter: params["filter"],
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
current_path: Controller.current_full_path(conn),
@@ -131,7 +131,7 @@ defmodule BlockScoutWeb.AddressController do
"_show_address_transactions.html",
address: address,
coin_balance_status: nil,
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
filter: params["filter"],
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
current_path: Controller.current_full_path(conn),
@@ -147,7 +147,7 @@ defmodule BlockScoutWeb.AddressController do
def address_counters(conn, %{"id" => address_hash_string}) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
- {validation_count} = Chain.address_counters(address)
+ {validation_count} = Counters.address_counters(address)
transactions_from_db = address.transactions_count || 0
token_transfers_from_db = address.token_transfers_count || 0
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex
index 292ea0f929c9..69c2d7dc0f6f 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex
@@ -6,7 +6,6 @@ defmodule BlockScoutWeb.AddressDecompiledContractController do
alias BlockScoutWeb.AccessHelper
alias Explorer.{Chain, Market}
- alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand
def index(conn, %{"address_id" => address_hash_string} = params) do
@@ -18,7 +17,7 @@ defmodule BlockScoutWeb.AddressDecompiledContractController do
"index.html",
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
tags: get_address_tags(address_hash, current_user(conn))
)
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex
index daca356e1ff0..92fbe1712b44 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex
@@ -12,7 +12,6 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
alias BlockScoutWeb.{AccessHelper, Controller, InternalTransactionView}
alias Explorer.{Chain, Market}
alias Explorer.Chain.{Address, Wei}
- alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View
@@ -86,7 +85,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
current_path: Controller.current_full_path(conn),
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
filter: params["filter"],
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
tags: get_address_tags(address_hash, current_user(conn))
@@ -113,7 +112,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
address: address,
filter: params["filter"],
coin_balance_status: nil,
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}),
current_path: Controller.current_full_path(conn),
tags: get_address_tags(address_hash, current_user(conn))
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex
index 67bbfc5958d9..fd7ee5a60970 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex
@@ -11,7 +11,6 @@ defmodule BlockScoutWeb.AddressLogsController do
alias BlockScoutWeb.{AccessHelper, AddressLogsView, Controller}
alias Explorer.{Chain, Market}
- alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View
@@ -21,7 +20,7 @@ defmodule BlockScoutWeb.AddressLogsController do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
:ok <- Chain.check_address_exists(address_hash),
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params) do
- logs_plus_one = Chain.address_to_logs(address_hash, paging_options(params))
+ logs_plus_one = Chain.address_to_logs(address_hash, false, paging_options(params))
{results, next_page} = split_list_by_page(logs_plus_one)
next_page_url =
@@ -67,7 +66,7 @@ defmodule BlockScoutWeb.AddressLogsController do
address: address,
current_path: Controller.current_full_path(conn),
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
tags: get_address_tags(address_hash, current_user(conn))
)
@@ -84,7 +83,7 @@ defmodule BlockScoutWeb.AddressLogsController do
formatted_topic = if String.starts_with?(topic, "0x"), do: topic, else: "0x" <> topic
- logs_plus_one = Chain.address_to_logs(address_hash, topic: formatted_topic)
+ logs_plus_one = Chain.address_to_logs(address_hash, false, topic: formatted_topic)
{results, next_page} = split_list_by_page(logs_plus_one)
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex
index b963cfa3e46c..ae534280cc74 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex
@@ -15,7 +15,6 @@ defmodule BlockScoutWeb.AddressReadContractController do
alias BlockScoutWeb.AddressView
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
- alias Explorer.ExchangeRates.Token
alias Explorer.SmartContract.Reader
alias Indexer.Fetcher.CoinBalanceOnDemand
@@ -40,7 +39,7 @@ defmodule BlockScoutWeb.AddressReadContractController do
type: :regular,
action: :read,
custom_abi: custom_abi?,
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null()
+ exchange_rate: Market.get_coin_exchange_rate()
]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex
index 6e4534af7ce9..5a4d9c981d04 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex
@@ -8,7 +8,6 @@ defmodule BlockScoutWeb.AddressReadProxyController do
alias BlockScoutWeb.AccessHelper
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
- alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand
def index(conn, %{"address_id" => address_hash_string} = params) do
@@ -33,7 +32,7 @@ defmodule BlockScoutWeb.AddressReadProxyController do
type: :proxy,
action: :read,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}),
tags: get_address_tags(address_hash, current_user(conn))
)
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_balance_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_balance_controller.ex
index 2013c7bcaad5..5899458fd887 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_balance_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_balance_controller.ex
@@ -9,12 +9,8 @@ defmodule BlockScoutWeb.AddressTokenBalanceController do
def index(conn, %{"address_id" => address_hash_string} = params) do
with true <- ajax?(conn),
{:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string) do
- token_balances =
- address_hash
- |> Chain.fetch_last_token_balances()
-
Task.start_link(fn ->
- TokenBalanceOnDemand.trigger_fetch(address_hash, token_balances)
+ TokenBalanceOnDemand.trigger_fetch(address_hash)
end)
case AccessHelper.restricted_access?(address_hash_string, params) do
@@ -24,7 +20,7 @@ defmodule BlockScoutWeb.AddressTokenBalanceController do
|> put_layout(false)
|> render("_token_balances.html",
address_hash: Address.checksum(address_hash),
- token_balances: token_balances,
+ token_balances: Chain.fetch_last_token_balances(address_hash),
conn: conn
)
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex
index f896e75e3fd2..d130043d8700 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex
@@ -1,14 +1,15 @@
defmodule BlockScoutWeb.AddressTokenController do
use BlockScoutWeb, :controller
- import BlockScoutWeb.Chain, only: [next_page_params: 3, paging_options: 1, split_list_by_page: 1]
+ import BlockScoutWeb.Chain,
+ only: [next_page_params: 4, paging_options: 1, split_list_by_page: 1, paging_params_with_fiat_value: 1]
+
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.{AccessHelper, AddressTokenView, Controller}
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
- alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View
@@ -23,7 +24,7 @@ defmodule BlockScoutWeb.AddressTokenController do
{tokens, next_page} = split_list_by_page(token_balances_plus_one)
next_page_path =
- case next_page_params(next_page, tokens, params) do
+ case next_page_params(next_page, tokens, params, &paging_params_with_fiat_value/1) do
nil ->
nil
@@ -33,12 +34,12 @@ defmodule BlockScoutWeb.AddressTokenController do
items =
tokens
- |> Enum.map(fn {token_balance, token} ->
+ |> Enum.map(fn token_balance ->
View.render_to_string(
AddressTokenView,
"_tokens.html",
token_balance: token_balance,
- token: token,
+ token: token_balance.token,
address: address,
conn: conn
)
@@ -73,7 +74,7 @@ defmodule BlockScoutWeb.AddressTokenController do
address: address,
current_path: Controller.current_full_path(conn),
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}),
tags: get_address_tags(address_hash, current_user(conn))
)
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex
index dff9c285419d..94f87de09348 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex
@@ -5,15 +5,16 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.{AccessHelper, Controller, TransactionView}
- alias Explorer.ExchangeRates.Token
alias Explorer.{Chain, Market}
- alias Explorer.Chain.Address
+ alias Explorer.Chain.{Address, DenormalizationHelper}
alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View
import BlockScoutWeb.Chain,
only: [current_filter: 1, next_page_params: 3, paging_options: 1, split_list_by_page: 1]
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
+
@transaction_necessity_by_association [
necessity_by_association: %{
[created_contract_address: :names] => :optional,
@@ -25,12 +26,11 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
[token_transfers: :token] => :optional,
[token_transfers: :to_address] => :optional,
[token_transfers: :from_address] => :optional,
- [token_transfers: :token_contract_address] => :optional,
- :block => :required
+ [token_transfers: :token_contract_address] => :optional
}
]
- {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
+ {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string())
@burn_address_hash burn_address_hash
def index(
@@ -109,7 +109,8 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
"index.html",
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
+ filter: params["filter"],
current_path: Controller.current_full_path(conn),
token: token,
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}),
@@ -139,6 +140,7 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params) do
options =
@transaction_necessity_by_association
+ |> DenormalizationHelper.extend_block_necessity(:required)
|> Keyword.merge(paging_options(params))
|> Keyword.merge(current_filter(params))
@@ -201,7 +203,7 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
"index.html",
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
filter: params["filter"],
current_path: Controller.current_full_path(conn),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}),
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
index 466c0455a962..05ef0c98d7fe 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
@@ -6,10 +6,9 @@ defmodule BlockScoutWeb.AddressTransactionController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
-
import BlockScoutWeb.Chain, only: [current_filter: 1, paging_options: 1, next_page_params: 3, split_list_by_page: 1]
-
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
alias BlockScoutWeb.{AccessHelper, Controller, TransactionView}
alias Explorer.{Chain, Market}
@@ -21,9 +20,8 @@ defmodule BlockScoutWeb.AddressTransactionController do
AddressTransactionCsvExporter
}
- alias Explorer.Chain.Wei
+ alias Explorer.Chain.{DenormalizationHelper, Transaction, Wei}
- alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View
@@ -34,14 +32,13 @@ defmodule BlockScoutWeb.AddressTransactionController do
[created_contract_address: :names] => :optional,
[from_address: :names] => :optional,
[to_address: :names] => :optional,
- :block => :optional,
[created_contract_address: :smart_contract] => :optional,
[from_address: :smart_contract] => :optional,
[to_address: :smart_contract] => :optional
}
]
- {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
+ {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string())
@burn_address_hash burn_address_hash
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
@@ -52,10 +49,11 @@ defmodule BlockScoutWeb.AddressTransactionController do
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params) do
options =
@transaction_necessity_by_association
+ |> DenormalizationHelper.extend_block_necessity(:optional)
|> Keyword.merge(paging_options(params))
|> Keyword.merge(current_filter(params))
- results_plus_one = Chain.address_to_transactions_with_rewards(address_hash, options)
+ results_plus_one = Transaction.address_to_transactions_with_rewards(address_hash, options)
{results, next_page} = split_list_by_page(results_plus_one)
next_page_url =
@@ -124,7 +122,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
"index.html",
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
filter: params["filter"],
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
current_path: Controller.current_full_path(conn),
@@ -154,7 +152,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
"index.html",
address: address,
coin_balance_status: nil,
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
filter: params["filter"],
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
current_path: Controller.current_full_path(conn),
@@ -187,15 +185,18 @@ defmodule BlockScoutWeb.AddressTransactionController do
"from_period" => from_period,
"to_period" => to_period,
"recaptcha_response" => recaptcha_response
- },
+ } = params,
csv_export_module
)
when is_binary(address_hash_string) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
- {:ok, address} <- Chain.hash_to_address(address_hash),
+ {:address_exists, true} <- {:address_exists, Chain.address_exists?(address_hash)},
{:recaptcha, true} <- {:recaptcha, captcha_helper().recaptcha_passed?(recaptcha_response)} do
- address
- |> csv_export_module.export(from_period, to_period)
+ filter_type = Map.get(params, "filter_type")
+ filter_value = Map.get(params, "filter_value")
+
+ address_hash
+ |> csv_export_module.export(from_period, to_period, filter_type, filter_value)
|> Enum.reduce_while(put_resp_params(conn), fn chunk, conn ->
case Conn.chunk(conn, chunk) do
{:ok, conn} ->
@@ -209,7 +210,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
:error ->
unprocessable_entity(conn)
- {:error, :not_found} ->
+ {:address_exists, false} ->
not_found(conn)
{:recaptcha, false} ->
@@ -223,15 +224,18 @@ defmodule BlockScoutWeb.AddressTransactionController do
"address_id" => address_hash_string,
"from_period" => from_period,
"to_period" => to_period
- },
+ } = params,
csv_export_module
)
when is_binary(address_hash_string) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
- {:ok, address} <- Chain.hash_to_address(address_hash),
+ {:address_exists, true} <- {:address_exists, Chain.address_exists?(address_hash)},
true <- Application.get_env(:block_scout_web, :recaptcha)[:is_disabled] do
- address
- |> csv_export_module.export(from_period, to_period)
+ filter_type = Map.get(params, "filter_type")
+ filter_value = Map.get(params, "filter_value")
+
+ address_hash
+ |> csv_export_module.export(from_period, to_period, filter_type, filter_value)
|> Enum.reduce_while(put_resp_params(conn), fn chunk, conn ->
case Conn.chunk(conn, chunk) do
{:ok, conn} ->
@@ -245,7 +249,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
:error ->
unprocessable_entity(conn)
- {:error, :not_found} ->
+ {:address_exists, false} ->
not_found(conn)
false ->
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex
index a4893bf3cece..244deb3af04d 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex
@@ -12,7 +12,6 @@ defmodule BlockScoutWeb.AddressValidationController do
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.{AccessHelper, BlockView, Controller}
- alias Explorer.ExchangeRates.Token
alias Explorer.{Chain, Market}
alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View
@@ -83,7 +82,7 @@ defmodule BlockScoutWeb.AddressValidationController do
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
current_path: Controller.current_full_path(conn),
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
tags: get_address_tags(address_hash, current_user(conn))
)
else
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_withdrawal_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_withdrawal_controller.ex
index 7fcb05d5b40b..937899617445 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_withdrawal_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_withdrawal_controller.ex
@@ -16,7 +16,6 @@ defmodule BlockScoutWeb.AddressWithdrawalController do
alias Explorer.Chain.Wei
- alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View
@@ -78,7 +77,7 @@ defmodule BlockScoutWeb.AddressWithdrawalController do
"index.html",
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
current_path: Controller.current_full_path(conn),
tags: get_address_tags(address_hash, current_user(conn))
@@ -107,7 +106,7 @@ defmodule BlockScoutWeb.AddressWithdrawalController do
"index.html",
address: address,
coin_balance_status: nil,
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
current_path: Controller.current_full_path(conn),
tags: get_address_tags(address_hash, current_user(conn))
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex
index cd88e97a0bf9..32fc477ed283 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex
@@ -15,7 +15,6 @@ defmodule BlockScoutWeb.AddressWriteContractController do
alias BlockScoutWeb.AddressView
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
- alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand
def index(conn, %{"address_id" => address_hash_string} = params) do
@@ -35,7 +34,7 @@ defmodule BlockScoutWeb.AddressWriteContractController do
type: :regular,
action: :write,
custom_abi: custom_abi?,
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null()
+ exchange_rate: Market.get_coin_exchange_rate()
]
with false <- AddressView.contract_interaction_disabled?(),
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex
index 844c5d3ddcf4..68903261d6e3 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex
@@ -8,7 +8,6 @@ defmodule BlockScoutWeb.AddressWriteProxyController do
alias BlockScoutWeb.{AccessHelper, AddressView}
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
- alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand
def index(conn, %{"address_id" => address_hash_string} = params) do
@@ -34,7 +33,7 @@ defmodule BlockScoutWeb.AddressWriteProxyController do
type: :proxy,
action: :write,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}),
tags: get_address_tags(address_hash, current_user(conn))
)
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex
index fd80cf7ad77d..5939df0d2569 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex
@@ -83,7 +83,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
[]
end
- if Chain.smart_contract_fully_verified?(address_hash) do
+ if SmartContract.verified_with_full_match?(address_hash) do
render(conn, :error, error: @verified)
else
case Sourcify.check_by_address(address_hash) do
@@ -114,7 +114,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
} = params
) do
with {:check_verified_status, false} <-
- {:check_verified_status, Chain.smart_contract_fully_verified?(address_hash)},
+ {:check_verified_status, SmartContract.verified_with_full_match?(address_hash)},
{:format, {:ok, _casted_address_hash}} <- to_address_hash(address_hash),
{:params, {:ok, fetched_params}} <- {:params, fetch_verifysourcecode_params(params)},
uid <- VerificationStatus.generate_uid(address_hash) do
@@ -457,7 +457,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
_ = PublishHelper.check_and_verify(Hash.to_string(address_hash))
result =
- case Chain.address_hash_to_smart_contract(address_hash) do
+ case SmartContract.address_hash_to_smart_contract(address_hash) do
nil ->
:not_found
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex
index 53a5ec3add25..b27554eed39b 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex
@@ -29,7 +29,7 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslator do
end
def call(%Conn{params: %{"module" => module, "action" => action}} = conn, translations) do
- with true <- valid_api_request_path(conn),
+ with {:valid_api_request, true} <- {:valid_api_request, valid_api_request_path(conn)},
{:ok, {controller, write_actions}} <- translate_module(translations, module),
{:ok, action} <- translate_action(action),
true <- action_accessed?(action, write_actions),
@@ -58,6 +58,13 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslator do
:rate_limit_reached ->
AccessHelper.handle_rate_limit_deny(conn)
+ {:valid_api_request, false} ->
+ conn
+ |> put_status(404)
+ |> put_view(RPCView)
+ |> Controller.render(:error, error: "Not found")
+ |> halt()
+
_ ->
conn
|> put_status(500)
@@ -119,7 +126,8 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslator do
end
defp valid_api_request_path(conn) do
- if conn.request_path == "/api" || conn.request_path == "/api/v1" do
+ if conn.request_path == "/api" || conn.request_path == "/api/" || conn.request_path == "/api/v1" ||
+ conn.request_path == "/api/v1/" do
true
else
false
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex
index e8f829bbec14..19fcf8768c64 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex
@@ -3,8 +3,7 @@ defmodule BlockScoutWeb.API.RPC.StatsController do
use Explorer.Schema
- alias Explorer
- alias Explorer.{Chain, Etherscan, ExchangeRates}
+ alias Explorer.{Chain, Etherscan, Market}
alias Explorer.Chain.Cache.{AddressSum, AddressSumMinusBurnt}
alias Explorer.Chain.Wei
@@ -61,8 +60,7 @@ defmodule BlockScoutWeb.API.RPC.StatsController do
end
def coinprice(conn, _params) do
- symbol = Explorer.coin()
- rates = ExchangeRates.lookup(symbol)
+ rates = Market.get_coin_exchange_rate()
render(conn, "coinprice.json", rates: rates)
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex
index e14aef4b94e4..fbea15ca76f1 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex
@@ -4,7 +4,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
alias Explorer.Chain
- alias Explorer.Chain.Transaction
+ alias Explorer.Chain.{DenormalizationHelper, Transaction}
@api_true [api?: true]
@@ -75,7 +75,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do
end
defp transaction_from_hash(transaction_hash) do
- case Chain.hash_to_transaction(transaction_hash, necessity_by_association: %{block: :required}) do
+ case Chain.hash_to_transaction(transaction_hash, DenormalizationHelper.extend_block_necessity([], :required)) do
{:error, :not_found} -> {:transaction, :error}
{:ok, transaction} -> {:transaction, {:ok, transaction}}
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex
index 7b70b4b4276f..1c99dcb17136 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex
@@ -30,8 +30,8 @@ defmodule BlockScoutWeb.API.V1.GasPriceOracleController do
|> send_resp(status, result)
end
- def result(gas_prices) do
- gas_prices
+ defp result(gas_prices) do
+ %{slow: gas_prices[:slow][:price], average: gas_prices[:average][:price], fast: gas_prices[:fast][:price]}
|> Jason.encode!()
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex
index 923153b87fa4..f65bfcd3b5d9 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex
@@ -4,6 +4,8 @@ defmodule BlockScoutWeb.API.V1.HealthController do
alias Explorer.Chain
alias Timex.Duration
+ @ok_message "OK"
+
def health(conn, _) do
with {:ok, number, timestamp} <- Chain.last_db_block_status(),
{:ok, cache_number, cache_timestamp} <- Chain.last_cache_block_status() do
@@ -13,6 +15,16 @@ defmodule BlockScoutWeb.API.V1.HealthController do
end
end
+ def liveness(conn, _) do
+ send_resp(conn, :ok, @ok_message)
+ end
+
+ def readiness(conn, _) do
+ Chain.last_db_block_status()
+
+ send_resp(conn, :ok, @ok_message)
+ end
+
def result(number, timestamp, cache_number, cache_timestamp) do
%{
"healthy" => true,
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex
index 6608c246d062..2edc07e8dec8 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex
@@ -3,12 +3,13 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do
alias Explorer.Chain
alias Explorer.Chain.Hash.Address
+ alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.Solidity.Publisher
def create(conn, params) do
with {:ok, hash} <- validate_address_hash(params["address_hash"]),
:ok <- Chain.check_address_exists(hash),
- {:contract, :not_found} <- {:contract, Chain.check_verified_smart_contract_exists(hash)} do
+ {:contract, :not_found} <- {:contract, SmartContract.check_verified_smart_contract_exists(hash)} do
external_libraries = fetch_external_libraries(params)
case Publisher.publish(hash, params, external_libraries) do
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex
index 9b999f923476..fe0ec5bee1a2 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex
@@ -4,19 +4,30 @@ defmodule BlockScoutWeb.API.V2.AddressController do
import BlockScoutWeb.Chain,
only: [
next_page_params: 3,
+ next_page_params: 4,
token_transfers_next_page_params: 3,
paging_options: 1,
split_list_by_page: 1,
- current_filter: 1
+ current_filter: 1,
+ paging_params_with_fiat_value: 1
]
import BlockScoutWeb.PagingHelper,
- only: [delete_parameters_from_next_page_params: 1, token_transfers_types_options: 1]
+ only: [
+ delete_parameters_from_next_page_params: 1,
+ token_transfers_types_options: 1,
+ address_transactions_sorting: 1,
+ nft_token_types_options: 1
+ ]
+
+ import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1, maybe_preload_ens_to_address: 1]
alias BlockScoutWeb.AccessHelper
alias BlockScoutWeb.API.V2.{BlockView, TransactionView, WithdrawalView}
- alias Explorer.ExchangeRates.Token
alias Explorer.{Chain, Market}
+ alias Explorer.Chain.{Address, Hash, Transaction}
+ alias Explorer.Chain.Address.Counters
+ alias Explorer.Chain.Token.Instance
alias Indexer.Fetcher.{CoinBalanceOnDemand, TokenBalanceOnDemand}
@transaction_necessity_by_association [
@@ -44,36 +55,43 @@ defmodule BlockScoutWeb.API.V2.AddressController do
@address_options [
necessity_by_association: %{
- :contracts_creation_internal_transaction => :optional,
:names => :optional,
- :smart_contract => :optional,
- :token => :optional,
- :contracts_creation_transaction => :optional
+ :token => :optional
},
api?: true
]
+ @contract_address_preloads [
+ :smart_contract,
+ :contracts_creation_internal_transaction,
+ :contracts_creation_transaction
+ ]
+
+ @nft_necessity_by_association [
+ necessity_by_association: %{
+ :token => :optional
+ }
+ ]
+
@api_true [api?: true]
action_fallback(BlockScoutWeb.API.V2.FallbackController)
- def address(conn, %{"address_hash" => address_hash_string} = params) do
- with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
- {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:not_found, {:ok, address}} <- {:not_found, Chain.hash_to_address(address_hash, @address_options)} do
- CoinBalanceOnDemand.trigger_fetch(address)
+ def address(conn, %{"address_hash_param" => address_hash_string} = params) do
+ with {:ok, _address_hash, address} <- validate_address(address_hash_string, params, @address_options),
+ fully_preloaded_address <-
+ Address.maybe_preload_smart_contract_associations(address, @contract_address_preloads, @api_true) do
+ CoinBalanceOnDemand.trigger_fetch(fully_preloaded_address)
conn
|> put_status(200)
- |> render(:address, %{address: address})
+ |> render(:address, %{address: fully_preloaded_address |> maybe_preload_ens_to_address()})
end
end
- def counters(conn, %{"address_hash" => address_hash_string} = params) do
- with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
- {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:not_found, {:ok, address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do
- {validation_count} = Chain.address_counters(address, @api_true)
+ def counters(conn, %{"address_hash_param" => address_hash_string} = params) do
+ with {:ok, _address_hash, address} <- validate_address(address_hash_string, params) do
+ {validation_count} = Counters.address_counters(address, @api_true)
transactions_from_db = address.transactions_count || 0
token_transfers_from_db = address.token_transfers_count || 0
@@ -88,16 +106,14 @@ defmodule BlockScoutWeb.API.V2.AddressController do
end
end
- def token_balances(conn, %{"address_hash" => address_hash_string} = params) do
- with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
- {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do
+ def token_balances(conn, %{"address_hash_param" => address_hash_string} = params) do
+ with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do
token_balances =
address_hash
|> Chain.fetch_last_token_balances(@api_true)
Task.start_link(fn ->
- TokenBalanceOnDemand.trigger_fetch(address_hash, token_balances)
+ TokenBalanceOnDemand.trigger_fetch(address_hash)
end)
conn
@@ -106,38 +122,38 @@ defmodule BlockScoutWeb.API.V2.AddressController do
end
end
- def transactions(conn, %{"address_hash" => address_hash_string} = params) do
- with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
- {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do
+ def transactions(conn, %{"address_hash_param" => address_hash_string} = params) do
+ with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do
options =
@transaction_necessity_by_association
|> Keyword.merge(paging_options(params))
|> Keyword.merge(current_filter(params))
+ |> Keyword.merge(address_transactions_sorting(params))
- results_plus_one = Chain.address_to_transactions_with_rewards(address_hash, options)
+ results_plus_one = Transaction.address_to_transactions_without_rewards(address_hash, options, false)
{transactions, next_page} = split_list_by_page(results_plus_one)
next_page_params =
- next_page |> next_page_params(transactions, params) |> delete_parameters_from_next_page_params()
+ next_page
+ |> next_page_params(
+ transactions,
+ delete_parameters_from_next_page_params(params),
+ &Transaction.address_transactions_next_page_params/1
+ )
conn
|> put_status(200)
|> put_view(TransactionView)
- |> render(:transactions, %{transactions: transactions, next_page_params: next_page_params})
+ |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), next_page_params: next_page_params})
end
end
def token_transfers(
conn,
- %{"address_hash" => address_hash_string, "token" => token_address_hash_string} = params
+ %{"address_hash_param" => address_hash_string, "token" => token_address_hash_string} = params
) do
- with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
- {:format, {:ok, token_address_hash}} <- {:format, Chain.string_to_address_hash(token_address_hash_string)},
- {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:ok, false} <- AccessHelper.restricted_access?(token_address_hash_string, params),
- {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)},
- {:not_found, {:ok, _}} <- {:not_found, Chain.token_from_address_hash(token_address_hash, @api_true)} do
+ with {:ok, address_hash, _address} <- validate_address(address_hash_string, params),
+ {:ok, token_address_hash, _token_address} <- validate_address(token_address_hash_string, params) do
paging_options = paging_options(params)
options =
@@ -166,20 +182,20 @@ defmodule BlockScoutWeb.API.V2.AddressController do
next_page_params =
next_page
- |> token_transfers_next_page_params(token_transfers, params)
- |> delete_parameters_from_next_page_params()
+ |> token_transfers_next_page_params(token_transfers, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
|> put_view(TransactionView)
- |> render(:token_transfers, %{token_transfers: token_transfers, next_page_params: next_page_params})
+ |> render(:token_transfers, %{
+ token_transfers: token_transfers |> maybe_preload_ens(),
+ next_page_params: next_page_params
+ })
end
end
- def token_transfers(conn, %{"address_hash" => address_hash_string} = params) do
- with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
- {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do
+ def token_transfers(conn, %{"address_hash_param" => address_hash_string} = params) do
+ with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do
paging_options = paging_options(params)
options =
@@ -198,20 +214,20 @@ defmodule BlockScoutWeb.API.V2.AddressController do
next_page_params =
next_page
- |> token_transfers_next_page_params(token_transfers, params)
- |> delete_parameters_from_next_page_params()
+ |> token_transfers_next_page_params(token_transfers, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
|> put_view(TransactionView)
- |> render(:token_transfers, %{token_transfers: token_transfers, next_page_params: next_page_params})
+ |> render(:token_transfers, %{
+ token_transfers: token_transfers |> maybe_preload_ens(),
+ next_page_params: next_page_params
+ })
end
end
- def internal_transactions(conn, %{"address_hash" => address_hash_string} = params) do
- with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
- {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do
+ def internal_transactions(conn, %{"address_hash_param" => address_hash_string} = params) do
+ with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do
full_options =
[
necessity_by_association: %{
@@ -231,64 +247,58 @@ defmodule BlockScoutWeb.API.V2.AddressController do
{internal_transactions, next_page} = split_list_by_page(results_plus_one)
next_page_params =
- next_page |> next_page_params(internal_transactions, params) |> delete_parameters_from_next_page_params()
+ next_page |> next_page_params(internal_transactions, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
|> put_view(TransactionView)
|> render(:internal_transactions, %{
- internal_transactions: internal_transactions,
+ internal_transactions: internal_transactions |> maybe_preload_ens(),
next_page_params: next_page_params
})
end
end
- def logs(conn, %{"address_hash" => address_hash_string, "topic" => topic} = params) do
- with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
- {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do
+ def logs(conn, %{"address_hash_param" => address_hash_string, "topic" => topic} = params) do
+ with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do
prepared_topic = String.trim(topic)
formatted_topic = if String.starts_with?(prepared_topic, "0x"), do: prepared_topic, else: "0x" <> prepared_topic
- options = Keyword.merge([topic: formatted_topic], @api_true)
+ options = params |> paging_options() |> Keyword.merge(topic: formatted_topic) |> Keyword.merge(@api_true)
- results_plus_one = Chain.address_to_logs(address_hash, options)
+ results_plus_one = Chain.address_to_logs(address_hash, false, options)
{logs, next_page} = split_list_by_page(results_plus_one)
- next_page_params = next_page |> next_page_params(logs, params) |> delete_parameters_from_next_page_params()
+ next_page_params = next_page |> next_page_params(logs, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
|> put_view(TransactionView)
- |> render(:logs, %{logs: logs, next_page_params: next_page_params})
+ |> render(:logs, %{logs: logs |> maybe_preload_ens(), next_page_params: next_page_params})
end
end
- def logs(conn, %{"address_hash" => address_hash_string} = params) do
- with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
- {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do
+ def logs(conn, %{"address_hash_param" => address_hash_string} = params) do
+ with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do
options = params |> paging_options() |> Keyword.merge(@api_true)
- results_plus_one = Chain.address_to_logs(address_hash, options)
+ results_plus_one = Chain.address_to_logs(address_hash, false, options)
{logs, next_page} = split_list_by_page(results_plus_one)
- next_page_params = next_page |> next_page_params(logs, params) |> delete_parameters_from_next_page_params()
+ next_page_params = next_page |> next_page_params(logs, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
|> put_view(TransactionView)
- |> render(:logs, %{logs: logs, next_page_params: next_page_params})
+ |> render(:logs, %{logs: logs |> maybe_preload_ens(), next_page_params: next_page_params})
end
end
- def blocks_validated(conn, %{"address_hash" => address_hash_string} = params) do
- with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
- {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do
+ def blocks_validated(conn, %{"address_hash_param" => address_hash_string} = params) do
+ with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do
full_options =
[
necessity_by_association: %{
@@ -304,7 +314,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do
results_plus_one = Chain.get_blocks_validated_by_address(full_options, address_hash)
{blocks, next_page} = split_list_by_page(results_plus_one)
- next_page_params = next_page |> next_page_params(blocks, params) |> delete_parameters_from_next_page_params()
+ next_page_params = next_page |> next_page_params(blocks, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
@@ -313,18 +323,17 @@ defmodule BlockScoutWeb.API.V2.AddressController do
end
end
- def coin_balance_history(conn, %{"address_hash" => address_hash_string} = params) do
+ def coin_balance_history(conn, %{"address_hash_param" => address_hash_string} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do
+ {:not_found, {:ok, address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do
full_options = params |> paging_options() |> Keyword.merge(@api_true)
- results_plus_one = Chain.address_to_coin_balances(address_hash, full_options)
+ results_plus_one = Chain.address_to_coin_balances(address, full_options)
{coin_balances, next_page} = split_list_by_page(results_plus_one)
- next_page_params =
- next_page |> next_page_params(coin_balances, params) |> delete_parameters_from_next_page_params()
+ next_page_params = next_page |> next_page_params(coin_balances, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
@@ -332,10 +341,8 @@ defmodule BlockScoutWeb.API.V2.AddressController do
end
end
- def coin_balance_history_by_day(conn, %{"address_hash" => address_hash_string} = params) do
- with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
- {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do
+ def coin_balance_history_by_day(conn, %{"address_hash_param" => address_hash_string} = params) do
+ with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do
balances_by_day =
address_hash
|> Chain.address_to_balances_by_day(@api_true)
@@ -346,23 +353,30 @@ defmodule BlockScoutWeb.API.V2.AddressController do
end
end
- def tokens(conn, %{"address_hash" => address_hash_string} = params) do
- with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
- {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do
+ def tokens(conn, %{"address_hash_param" => address_hash_string} = params) do
+ with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do
results_plus_one =
address_hash
|> Chain.fetch_paginated_last_token_balances(
params
- |> delete_parameters_from_next_page_params()
|> paging_options()
|> Keyword.merge(token_transfers_types_options(params))
|> Keyword.merge(@api_true)
)
+ Task.start_link(fn ->
+ TokenBalanceOnDemand.trigger_fetch(address_hash)
+ end)
+
{tokens, next_page} = split_list_by_page(results_plus_one)
- next_page_params = next_page |> next_page_params(tokens, params) |> delete_parameters_from_next_page_params()
+ next_page_params =
+ next_page
+ |> next_page_params(
+ tokens,
+ delete_parameters_from_next_page_params(params),
+ &paging_params_with_fiat_value/1
+ )
conn
|> put_status(200)
@@ -370,20 +384,18 @@ defmodule BlockScoutWeb.API.V2.AddressController do
end
end
- def withdrawals(conn, %{"address_hash" => address_hash_string} = params) do
- with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
- {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do
+ def withdrawals(conn, %{"address_hash_param" => address_hash_string} = params) do
+ with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do
options = @api_true |> Keyword.merge(paging_options(params))
withdrawals_plus_one = address_hash |> Chain.address_hash_to_withdrawals(options)
{withdrawals, next_page} = split_list_by_page(withdrawals_plus_one)
- next_page_params = next_page |> next_page_params(withdrawals, params) |> delete_parameters_from_next_page_params()
+ next_page_params = next_page |> next_page_params(withdrawals, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
|> put_view(WithdrawalView)
- |> render(:withdrawals, %{withdrawals: withdrawals, next_page_params: next_page_params})
+ |> render(:withdrawals, %{withdrawals: withdrawals |> maybe_preload_ens(), next_page_params: next_page_params})
end
end
@@ -392,21 +404,112 @@ defmodule BlockScoutWeb.API.V2.AddressController do
params
|> paging_options()
|> Keyword.merge(@api_true)
- |> Chain.list_top_addresses()
+ |> Address.list_top_addresses()
|> split_list_by_page()
next_page_params = next_page_params(next_page, addresses, params)
- exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null()
+ exchange_rate = Market.get_coin_exchange_rate()
total_supply = Chain.total_supply()
conn
|> put_status(200)
|> render(:addresses, %{
- addresses: addresses,
+ addresses: addresses |> maybe_preload_ens(),
next_page_params: next_page_params,
exchange_rate: exchange_rate,
total_supply: total_supply
})
end
+
+ def tabs_counters(conn, %{"address_hash_param" => address_hash_string} = params) do
+ with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do
+ {validations, transactions, token_transfers, token_balances, logs, withdrawals, internal_txs} =
+ Counters.address_limited_counters(address_hash, @api_true)
+
+ conn
+ |> put_status(200)
+ |> json(%{
+ validations_count: validations,
+ transactions_count: transactions,
+ token_transfers_count: token_transfers,
+ token_balances_count: token_balances,
+ logs_count: logs,
+ withdrawals_count: withdrawals,
+ internal_txs_count: internal_txs
+ })
+ end
+ end
+
+ def nft_list(conn, %{"address_hash_param" => address_hash_string} = params) do
+ with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do
+ results_plus_one =
+ Instance.nft_list(
+ address_hash,
+ params
+ |> paging_options()
+ |> Keyword.merge(nft_token_types_options(params))
+ |> Keyword.merge(@api_true)
+ |> Keyword.merge(@nft_necessity_by_association)
+ )
+
+ {nfts, next_page} = split_list_by_page(results_plus_one)
+
+ next_page_params =
+ next_page
+ |> next_page_params(
+ nfts,
+ delete_parameters_from_next_page_params(params),
+ &Instance.nft_list_next_page_params/1
+ )
+
+ conn
+ |> put_status(200)
+ |> render(:nft_list, %{token_instances: nfts, next_page_params: next_page_params})
+ end
+ end
+
+ def nft_collections(conn, %{"address_hash_param" => address_hash_string} = params) do
+ with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do
+ results_plus_one =
+ Instance.nft_collections(
+ address_hash,
+ params
+ |> paging_options()
+ |> Keyword.merge(nft_token_types_options(params))
+ |> Keyword.merge(@api_true)
+ |> Keyword.merge(@nft_necessity_by_association)
+ )
+
+ {collections, next_page} = split_list_by_page(results_plus_one)
+
+ next_page_params =
+ next_page
+ |> next_page_params(
+ collections,
+ delete_parameters_from_next_page_params(params),
+ &Instance.nft_collections_next_page_params/1
+ )
+
+ conn
+ |> put_status(200)
+ |> render(:nft_collections, %{collections: collections, next_page_params: next_page_params})
+ end
+ end
+
+ @doc """
+ Checks if this valid address hash string, and this address is not prohibited address
+ """
+ @spec validate_address(String.t(), any(), Keyword.t()) ::
+ {:format, :error}
+ | {:not_found, {:error, :not_found}}
+ | {:restricted_access, true}
+ | {:ok, Hash.t(), Address.t()}
+ def validate_address(address_hash_string, params, options \\ @api_true) do
+ with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
+ {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
+ {:not_found, {:ok, address}} <- {:not_found, Chain.hash_to_address(address_hash, options, false)} do
+ {:ok, address_hash, address}
+ end
+ end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/api_key_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/api_key_controller.ex
index 9b1fc2869b59..4135a6bba9fb 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/api_key_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/api_key_controller.ex
@@ -2,10 +2,17 @@ defmodule BlockScoutWeb.API.V2.APIKeyController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.AccessHelper
- alias Plug.Crypto
+
+ @api_v2_temp_token_key Application.compile_env(:block_scout_web, :api_v2_temp_token_key)
action_fallback(BlockScoutWeb.API.V2.FallbackController)
+ plug(:fetch_cookies, signed: [@api_v2_temp_token_key])
+
+ @doc """
+ Function to handle POST requests to `/api/v2/key` endpoint. It expects body with `recaptcha_response`. And puts cookie with temporary API v2 token. Which is handled here: https://github.com/blockscout/blockscout/blob/cd19739347f267d8a6ad81bbba2dbdad08bcc134/apps/block_scout_web/lib/block_scout_web/views/access_helper.ex#L170
+ """
+ @spec get_key(Plug.Conn.t(), nil | map) :: {:recaptcha, any} | Plug.Conn.t()
def get_key(conn, params) do
helper = Application.get_env(:block_scout_web, :captcha_helper)
ttl = Application.get_env(:block_scout_web, :api_rate_limit)[:api_v2_token_ttl_seconds]
@@ -14,11 +21,14 @@ defmodule BlockScoutWeb.API.V2.APIKeyController do
{:recaptcha, false} <- {:recaptcha, is_nil(recaptcha_response)},
{:recaptcha, true} <- {:recaptcha, helper.recaptcha_passed?(recaptcha_response)} do
conn
+ |> put_resp_cookie(@api_v2_temp_token_key, %{ip: AccessHelper.conn_to_ip_string(conn)},
+ max_age: ttl,
+ sign: true,
+ same_site: "Lax",
+ domain: Application.get_env(:block_scout_web, :cookie_domain)
+ )
|> json(%{
- key:
- Crypto.sign(conn.secret_key_base, conn.secret_key_base, %{ip: AccessHelper.conn_to_ip_string(conn)},
- max_age: ttl
- )
+ message: "OK"
})
end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex
index 11492b242027..49e21dcce202 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex
@@ -11,6 +11,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do
]
import BlockScoutWeb.PagingHelper, only: [delete_parameters_from_next_page_params: 1, select_block_type: 1]
+ import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1]
alias BlockScoutWeb.API.V2.{TransactionView, WithdrawalView}
alias Explorer.Chain
@@ -35,7 +36,8 @@ defmodule BlockScoutWeb.API.V2.BlockController do
:uncles => :optional,
:nephews => :optional,
:rewards => :optional,
- :transactions => :optional
+ :transactions => :optional,
+ :withdrawals => :optional
},
api?: true
]
@@ -76,11 +78,11 @@ defmodule BlockScoutWeb.API.V2.BlockController do
{blocks, next_page} = split_list_by_page(blocks_plus_one)
- next_page_params = next_page |> next_page_params(blocks, params) |> delete_parameters_from_next_page_params()
+ next_page_params = next_page |> next_page_params(blocks, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
- |> render(:blocks, %{blocks: blocks, next_page_params: next_page_params})
+ |> render(:blocks, %{blocks: blocks |> maybe_preload_ens(), next_page_params: next_page_params})
end
def transactions(conn, %{"block_hash_or_number" => block_hash_or_number} = params) do
@@ -97,13 +99,12 @@ defmodule BlockScoutWeb.API.V2.BlockController do
next_page_params =
next_page
- |> next_page_params(transactions, params)
- |> delete_parameters_from_next_page_params()
+ |> next_page_params(transactions, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
|> put_view(TransactionView)
- |> render(:transactions, %{transactions: transactions, next_page_params: next_page_params})
+ |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), next_page_params: next_page_params})
end
end
@@ -117,12 +118,12 @@ defmodule BlockScoutWeb.API.V2.BlockController do
withdrawals_plus_one = Chain.block_to_withdrawals(block.hash, full_options)
{withdrawals, next_page} = split_list_by_page(withdrawals_plus_one)
- next_page_params = next_page |> next_page_params(withdrawals, params) |> delete_parameters_from_next_page_params()
+ next_page_params = next_page |> next_page_params(withdrawals, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
|> put_view(WithdrawalView)
- |> render(:withdrawals, %{withdrawals: withdrawals, next_page_params: next_page_params})
+ |> render(:withdrawals, %{withdrawals: withdrawals |> maybe_preload_ens(), next_page_params: next_page_params})
end
end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex
index 78ef0980d2c5..c7f1fc3692ee 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex
@@ -1,134 +1,267 @@
defmodule BlockScoutWeb.API.V2.FallbackController do
use Phoenix.Controller
+ require Logger
+
alias BlockScoutWeb.API.V2.ApiView
- def call(conn, {:format, _}) do
+ @verification_failed "API v2 smart-contract verification failed"
+ @invalid_parameters "Invalid parameter(s)"
+ @invalid_address_hash "Invalid address hash"
+ @invalid_hash "Invalid hash"
+ @invalid_number "Invalid number"
+ @invalid_url "Invalid URL"
+ @not_found "Not found"
+ @contract_interaction_disabled "Contract interaction disabled"
+ @restricted_access "Restricted access"
+ @already_verified "Already verified"
+ @json_not_found "JSON files not found"
+ @error_while_reading_json "Error while reading JSON file"
+ @error_in_libraries "Libraries are not valid JSON map"
+ @block_lost_consensus "Block lost consensus"
+ @invalid_captcha_resp "Invalid reCAPTCHA response"
+ @unauthorized "Unauthorized"
+ @not_configured_api_key "API key not configured on the server"
+ @wrong_api_key "Wrong API key"
+ @address_not_found "Address not found"
+ @address_is_not_smart_contract "Address is not smart-contract"
+ @empty_response "Empty response"
+ @tx_interpreter_service_disabled "Transaction Interpretation Service is not enabled"
+
+ def call(conn, {:format, _params}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: #{@invalid_parameters}"]
+ end)
+
conn
|> put_status(:unprocessable_entity)
|> put_view(ApiView)
- |> render(:message, %{message: "Invalid parameter(s)"})
+ |> render(:message, %{message: @invalid_parameters})
end
def call(conn, {:format_address, _}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: #{@invalid_address_hash}"]
+ end)
+
conn
|> put_status(:unprocessable_entity)
|> put_view(ApiView)
- |> render(:message, %{message: "Invalid address hash"})
+ |> render(:message, %{message: @invalid_address_hash})
end
def call(conn, {:format_url, _}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: #{@invalid_url}"]
+ end)
+
conn
|> put_status(:unprocessable_entity)
|> put_view(ApiView)
- |> render(:message, %{message: "Invalid URL"})
+ |> render(:message, %{message: @invalid_url})
end
def call(conn, {:not_found, _, :empty_items_with_next_page_params}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: :empty_items_with_next_page_params"]
+ end)
+
conn
|> json(%{"items" => [], "next_page_params" => nil})
end
def call(conn, {:not_found, _}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: #{@not_found}"]
+ end)
+
conn
|> put_status(:not_found)
|> put_view(ApiView)
- |> render(:message, %{message: "Not found"})
+ |> render(:message, %{message: @not_found})
end
def call(conn, {:contract_interaction_disabled, _}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: #{@contract_interaction_disabled}"]
+ end)
+
conn
|> put_status(:forbidden)
|> put_view(ApiView)
- |> render(:message, %{message: "Contract interaction disabled"})
+ |> render(:message, %{message: @contract_interaction_disabled})
end
def call(conn, {:error, {:invalid, :hash}}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: #{@invalid_hash}"]
+ end)
+
conn
|> put_status(:unprocessable_entity)
|> put_view(ApiView)
- |> render(:message, %{message: "Invalid hash"})
+ |> render(:message, %{message: @invalid_hash})
end
def call(conn, {:error, {:invalid, :number}}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: #{@invalid_number}"]
+ end)
+
conn
|> put_status(:unprocessable_entity)
|> put_view(ApiView)
- |> render(:message, %{message: "Invalid number"})
+ |> render(:message, %{message: @invalid_number})
end
def call(conn, {:error, :not_found}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: :not_found"]
+ end)
+
conn
|> call({:not_found, nil})
end
def call(conn, {:restricted_access, true}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: #{@restricted_access}"]
+ end)
+
conn
|> put_status(:forbidden)
|> put_view(ApiView)
- |> render(:message, %{message: "Restricted access"})
+ |> render(:message, %{message: @restricted_access})
end
- def call(conn, {:already_verified, true}) do
+ def call(conn, {:already_verified, _}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: #{@already_verified}"]
+ end)
+
conn
|> put_view(ApiView)
- |> render(:message, %{message: "Already verified"})
+ |> render(:message, %{message: @already_verified})
end
def call(conn, {:no_json_file, _}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: #{@json_not_found}"]
+ end)
+
conn
|> put_view(ApiView)
- |> render(:message, %{message: "JSON files not found"})
+ |> render(:message, %{message: @json_not_found})
end
def call(conn, {:file_error, _}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: #{@error_while_reading_json}"]
+ end)
+
conn
|> put_view(ApiView)
- |> render(:message, %{message: "Error while reading JSON file"})
+ |> render(:message, %{message: @error_while_reading_json})
end
def call(conn, {:libs_format, _}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: #{@error_in_libraries}"]
+ end)
+
conn
|> put_view(ApiView)
- |> render(:message, %{message: "Libraries are not valid JSON map"})
+ |> render(:message, %{message: @error_in_libraries})
end
def call(conn, {:lost_consensus, {:ok, block}}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: #{@block_lost_consensus}"]
+ end)
+
conn
|> put_status(:not_found)
- |> json(%{message: "Block lost consensus", hash: to_string(block.hash)})
+ |> json(%{message: @block_lost_consensus, hash: to_string(block.hash)})
end
def call(conn, {:lost_consensus, {:error, :not_found}}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: #{@block_lost_consensus}"]
+ end)
+
conn
|> call({:not_found, nil})
end
def call(conn, {:recaptcha, _}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: #{@invalid_captcha_resp}"]
+ end)
+
conn
|> put_status(:forbidden)
|> put_view(ApiView)
- |> render(:message, %{message: "Invalid reCAPTCHA response"})
+ |> render(:message, %{message: @invalid_captcha_resp})
end
def call(conn, {:auth, _}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: #{@unauthorized}"]
+ end)
+
conn
|> put_status(:unauthorized)
|> put_view(ApiView)
- |> render(:message, %{message: "Unauthorized"})
+ |> render(:message, %{message: @unauthorized})
end
def call(conn, {:sensitive_endpoints_api_key, _}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: #{@not_configured_api_key}"]
+ end)
+
conn
|> put_status(:forbidden)
|> put_view(ApiView)
- |> render(:message, %{message: "API key not configured on the server"})
+ |> render(:message, %{message: @not_configured_api_key})
end
def call(conn, {:api_key, _}) do
+ Logger.error(fn ->
+ ["#{@verification_failed}: #{@wrong_api_key}"]
+ end)
+
conn
|> put_status(:unauthorized)
|> put_view(ApiView)
- |> render(:message, %{message: "Wrong API key"})
+ |> render(:message, %{message: @wrong_api_key})
+ end
+
+ def call(conn, {:address, {:error, :not_found}}) do
+ conn
+ |> put_status(:not_found)
+ |> put_view(ApiView)
+ |> render(:message, %{message: @address_not_found})
+ end
+
+ def call(conn, {:is_smart_contract, result}) when is_nil(result) or result == false do
+ conn
+ |> put_status(:not_found)
+ |> put_view(ApiView)
+ |> render(:message, %{message: @address_is_not_smart_contract})
+ end
+
+ def call(conn, {:is_empty_response, true}) do
+ conn
+ |> put_status(500)
+ |> put_view(ApiView)
+ |> render(:message, %{message: @empty_response})
+ end
+
+ def call(conn, {:tx_interpreter_enabled, false}) do
+ conn
+ |> put_status(404)
+ |> put_view(ApiView)
+ |> render(:message, %{message: @tx_interpreter_service_disabled})
end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/import_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/import_controller.ex
index 29175a1ad697..d8fefb4f2b5c 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/import_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/import_controller.ex
@@ -3,22 +3,39 @@ defmodule BlockScoutWeb.API.V2.ImportController do
alias BlockScoutWeb.API.V2.ApiView
alias Explorer.{Chain, Repo}
- alias Explorer.Chain.Token
+ alias Explorer.Chain.{Data, SmartContract, Token}
+ alias Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand
+ alias Explorer.SmartContract.EthBytecodeDBInterface
+
+ import Explorer.SmartContract.Helper, only: [prepare_bytecode_for_microservice: 3, contract_creation_input: 1]
require Logger
@api_true [api?: true]
action_fallback(BlockScoutWeb.API.V2.FallbackController)
- def import_token_info(conn, %{"iconUrl" => icon_url, "tokenAddress" => token_address_hash_string} = params) do
+ def import_token_info(
+ conn,
+ %{
+ "iconUrl" => icon_url,
+ "tokenAddress" => token_address_hash_string,
+ "tokenSymbol" => token_symbol,
+ "tokenName" => token_name
+ } = params
+ ) do
with {:sensitive_endpoints_api_key, api_key} when not is_nil(api_key) <-
{:sensitive_endpoints_api_key, Application.get_env(:block_scout_web, :sensitive_endpoints_api_key)},
{:api_key, ^api_key} <- {:api_key, params["api_key"]},
{:format_address, {:ok, address_hash}} <-
{:format_address, Chain.string_to_address_hash(token_address_hash_string)},
- {:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)},
- {:format_url, true} <- {:format_url, valid_url?(icon_url)} do
- case token |> Token.changeset(%{icon_url: icon_url}) |> Repo.update() do
+ {:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)} do
+ changeset =
+ %{is_verified_via_admin_panel: true}
+ |> put_icon_url(icon_url)
+ |> put_token_string_field(token_symbol, :symbol)
+ |> put_token_string_field(token_name, :name)
+
+ case token |> Token.changeset(changeset) |> Repo.update() do
{:ok, _} ->
conn
|> put_view(ApiView)
@@ -35,8 +52,81 @@ defmodule BlockScoutWeb.API.V2.ImportController do
end
end
+ @doc """
+ Function to handle request at:
+ `/api/v2/import/smart-contracts/{address_hash_param}`
+
+ Needed to try to import unverified smart contracts via eth-bytecode-db (`/api/v2/bytecodes/sources:search` method).
+ Protected by `x-api-key` header.
+ """
+ @spec try_to_search_contract(Plug.Conn.t(), map()) ::
+ {:already_verified, nil | SmartContract.t()}
+ | {:api_key, nil | binary()}
+ | {:format, :error}
+ | {:not_found, {:error, :not_found}}
+ | {:sensitive_endpoints_api_key, any()}
+ | Plug.Conn.t()
+ def try_to_search_contract(conn, %{"address_hash_param" => address_hash_string}) do
+ with {:sensitive_endpoints_api_key, api_key} when not is_nil(api_key) <-
+ {:sensitive_endpoints_api_key, Application.get_env(:block_scout_web, :sensitive_endpoints_api_key)},
+ {:api_key, ^api_key} <- {:api_key, get_api_key_header(conn)},
+ {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
+ {:not_found, {:ok, address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)},
+ {:already_verified, smart_contract} when is_nil(smart_contract) <-
+ {:already_verified, SmartContract.address_hash_to_smart_contract_without_twin(address_hash, @api_true)} do
+ creation_tx_input = contract_creation_input(address.hash)
+
+ with {:ok, %{"sourceType" => type} = source} <-
+ %{}
+ |> prepare_bytecode_for_microservice(creation_tx_input, Data.to_string(address.contract_code))
+ |> EthBytecodeDBInterface.search_contract_in_eth_bytecode_internal_db(),
+ {:ok, _} <- LookUpSmartContractSourcesOnDemand.process_contract_source(type, source, address.hash) do
+ conn
+ |> put_view(ApiView)
+ |> render(:message, %{message: "Success"})
+ else
+ _ ->
+ conn
+ |> put_view(ApiView)
+ |> render(:message, %{message: "Contract was not imported"})
+ end
+ end
+ end
+
defp valid_url?(url) when is_binary(url) do
uri = URI.parse(url)
uri.scheme != nil && uri.host =~ "."
end
+
+ defp valid_url?(_url), do: false
+
+ defp put_icon_url(changeset, icon_url) do
+ if valid_url?(icon_url) do
+ Map.put(changeset, :icon_url, icon_url)
+ else
+ changeset
+ end
+ end
+
+ defp put_token_string_field(changeset, token_symbol, field) when is_binary(token_symbol) do
+ token_symbol = String.trim(token_symbol)
+
+ if token_symbol !== "" do
+ Map.put(changeset, field, token_symbol)
+ else
+ changeset
+ end
+ end
+
+ defp put_token_string_field(changeset, _token_symbol, _field), do: changeset
+
+ defp get_api_key_header(conn) do
+ case get_req_header(conn, "x-api-key") do
+ [api_key] ->
+ api_key
+
+ _ ->
+ nil
+ end
+ end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex
index ceec1582e098..0e06f6e058ca 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex
@@ -6,6 +6,7 @@ defmodule BlockScoutWeb.API.V2.MainPageController do
alias Explorer.{Chain, Repo}
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
+ import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1]
@transactions_options [
necessity_by_association: %{
@@ -32,7 +33,7 @@ defmodule BlockScoutWeb.API.V2.MainPageController do
conn
|> put_status(200)
|> put_view(BlockView)
- |> render(:blocks, %{blocks: blocks})
+ |> render(:blocks, %{blocks: blocks |> maybe_preload_ens()})
end
def transactions(conn, _params) do
@@ -41,7 +42,7 @@ defmodule BlockScoutWeb.API.V2.MainPageController do
conn
|> put_status(200)
|> put_view(TransactionView)
- |> render(:transactions, %{transactions: recent_transactions})
+ |> render(:transactions, %{transactions: recent_transactions |> maybe_preload_ens()})
end
def watchlist_transactions(conn, _params) do
@@ -51,7 +52,10 @@ defmodule BlockScoutWeb.API.V2.MainPageController do
conn
|> put_status(200)
|> put_view(TransactionView)
- |> render(:transactions_watchlist, %{transactions: transactions, watchlist_names: watchlist_names})
+ |> render(:transactions_watchlist, %{
+ transactions: transactions |> maybe_preload_ens(),
+ watchlist_names: watchlist_names
+ })
end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/polygon_edge_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/polygon_edge_controller.ex
new file mode 100644
index 000000000000..53157d08bdbe
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/polygon_edge_controller.ex
@@ -0,0 +1,70 @@
+defmodule BlockScoutWeb.API.V2.PolygonEdgeController do
+ use BlockScoutWeb, :controller
+
+ import BlockScoutWeb.Chain,
+ only: [
+ next_page_params: 3,
+ paging_options: 1,
+ split_list_by_page: 1
+ ]
+
+ alias Explorer.Chain.PolygonEdge.Reader
+
+ action_fallback(BlockScoutWeb.API.V2.FallbackController)
+
+ @spec deposits(Plug.Conn.t(), map()) :: Plug.Conn.t()
+ def deposits(conn, params) do
+ {deposits, next_page} =
+ params
+ |> paging_options()
+ |> Keyword.put(:api?, true)
+ |> Reader.deposits()
+ |> split_list_by_page()
+
+ next_page_params = next_page_params(next_page, deposits, params)
+
+ conn
+ |> put_status(200)
+ |> render(:polygon_edge_deposits, %{
+ deposits: deposits,
+ next_page_params: next_page_params
+ })
+ end
+
+ @spec deposits_count(Plug.Conn.t(), map()) :: Plug.Conn.t()
+ def deposits_count(conn, _params) do
+ count = Reader.deposits_count(api?: true)
+
+ conn
+ |> put_status(200)
+ |> render(:polygon_edge_items_count, %{count: count})
+ end
+
+ @spec withdrawals(Plug.Conn.t(), map()) :: Plug.Conn.t()
+ def withdrawals(conn, params) do
+ {withdrawals, next_page} =
+ params
+ |> paging_options()
+ |> Keyword.put(:api?, true)
+ |> Reader.withdrawals()
+ |> split_list_by_page()
+
+ next_page_params = next_page_params(next_page, withdrawals, params)
+
+ conn
+ |> put_status(200)
+ |> render(:polygon_edge_withdrawals, %{
+ withdrawals: withdrawals,
+ next_page_params: next_page_params
+ })
+ end
+
+ @spec withdrawals_count(Plug.Conn.t(), map()) :: Plug.Conn.t()
+ def withdrawals_count(conn, _params) do
+ count = Reader.withdrawals_count(api?: true)
+
+ conn
+ |> put_status(200)
+ |> render(:polygon_edge_items_count, %{count: count})
+ end
+end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_conroller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_conroller.ex
new file mode 100644
index 000000000000..7c0e6a327714
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_conroller.ex
@@ -0,0 +1,61 @@
+defmodule BlockScoutWeb.API.V2.Proxy.NovesFiController do
+ use BlockScoutWeb, :controller
+
+ alias BlockScoutWeb.API.V2.{AddressController, TransactionController}
+ alias Explorer.ThirdPartyIntegrations.NovesFi
+
+ action_fallback(BlockScoutWeb.API.V2.FallbackController)
+
+ @doc """
+ Function to handle GET requests to `/api/v2/proxy/noves-fi/transactions/:transaction_hash_param` endpoint.
+ """
+ @spec transaction(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()}
+ def transaction(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do
+ with {:ok, _transaction, _transaction_hash} <-
+ TransactionController.validate_transaction(transaction_hash_string, params,
+ necessity_by_association: %{},
+ api?: true
+ ),
+ url = NovesFi.tx_url(transaction_hash_string),
+ {response, status} <- NovesFi.noves_fi_api_request(url, conn),
+ {:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do
+ conn
+ |> put_status(status)
+ |> json(response)
+ end
+ end
+
+ @doc """
+ Function to handle GET requests to `/api/v2/proxy/noves-fi/transactions/:transaction_hash_param/describe` endpoint.
+ """
+ @spec describe_transaction(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()}
+ def describe_transaction(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do
+ with {:ok, _transaction, _transaction_hash} <-
+ TransactionController.validate_transaction(transaction_hash_string, params,
+ necessity_by_association: %{},
+ api?: true
+ ),
+ url = NovesFi.describe_tx_url(transaction_hash_string),
+ {response, status} <- NovesFi.noves_fi_api_request(url, conn),
+ {:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do
+ conn
+ |> put_status(status)
+ |> json(response)
+ end
+ end
+
+ @doc """
+ Function to handle GET requests to `/api/v2/proxy/noves-fi/transactions/:transaction_hash_param/describe` endpoint.
+ """
+ @spec address_transactions(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()}
+ def address_transactions(conn, %{"address_hash_param" => address_hash_string} = params) do
+ with {:ok, _address_hash, _address} <- AddressController.validate_address(address_hash_string, params),
+ url = NovesFi.address_txs_url(address_hash_string),
+ {response, status} <- NovesFi.noves_fi_api_request(url, conn),
+ {:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do
+ conn
+ |> put_status(status)
+ |> json(response)
+ end
+ end
+end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex
index e76b4408e2f0..0a3c0f17aed4 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex
@@ -2,8 +2,12 @@ defmodule BlockScoutWeb.API.V2.SearchController do
use Phoenix.Controller
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1, from_param: 1]
+ import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens_info_to_search_results: 1]
- alias Explorer.Chain
+ alias Explorer.Chain.Search
+ alias Explorer.PagingOptions
+
+ @api_true [api?: true]
def search(conn, %{"q" => query} = params) do
[paging_options: paging_options] = paging_options(params)
@@ -11,7 +15,7 @@ defmodule BlockScoutWeb.API.V2.SearchController do
search_results_plus_one =
paging_options
- |> Chain.joint_search(offset, query, api?: true)
+ |> Search.joint_search(offset, query, @api_true)
{search_results, next_page} = split_list_by_page(search_results_plus_one)
@@ -19,7 +23,10 @@ defmodule BlockScoutWeb.API.V2.SearchController do
conn
|> put_status(200)
- |> render(:search_results, %{search_results: search_results, next_page_params: next_page_params})
+ |> render(:search_results, %{
+ search_results: search_results |> maybe_preload_ens_info_to_search_results(),
+ next_page_params: next_page_params
+ })
end
def check_redirect(conn, %{"q" => query}) do
@@ -32,4 +39,12 @@ defmodule BlockScoutWeb.API.V2.SearchController do
|> put_status(200)
|> render(:search_results, %{result: result})
end
+
+ def quick_search(conn, %{"q" => query}) do
+ search_results = Search.balanced_unpaginated_search(%PagingOptions{page_size: 50}, query, @api_true)
+
+ conn
+ |> put_status(200)
+ |> render(:search_results, %{search_results: search_results |> maybe_preload_ens_info_to_search_results()})
+ end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex
index dc294bed7386..4986c3a19ad5 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex
@@ -4,16 +4,18 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
import BlockScoutWeb.PagingHelper,
- only: [current_filter: 1, delete_parameters_from_next_page_params: 1, search_query: 1]
+ only: [current_filter: 1, delete_parameters_from_next_page_params: 1, search_query: 1, smart_contracts_sorting: 1]
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
import Explorer.SmartContract.Solidity.Verifier, only: [parse_boolean: 1]
alias BlockScoutWeb.{AccessHelper, AddressView}
alias Ecto.Association.NotLoaded
alias Explorer.Chain
- alias Explorer.Chain.SmartContract
+ alias Explorer.Chain.{Address, SmartContract}
alias Explorer.SmartContract.{Reader, Writer}
alias Explorer.SmartContract.Solidity.PublishHelper
+ alias Explorer.ThirdPartyIntegrations.SolidityScan
@smart_contract_address_options [
necessity_by_association: %{
@@ -26,16 +28,14 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
@api_true [api?: true]
- @burn_address "0x0000000000000000000000000000000000000000"
-
action_fallback(BlockScoutWeb.API.V2.FallbackController)
def smart_contract(conn, %{"address_hash" => address_hash_string} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- _ <- PublishHelper.check_and_verify(address_hash_string),
+ _ <- PublishHelper.sourcify_check(address_hash_string),
{:not_found, {:ok, address}} <-
- {:not_found, Chain.find_contract_address(address_hash, @smart_contract_address_options, true)} do
+ {:not_found, Chain.find_contract_address(address_hash, @smart_contract_address_options, false)} do
conn
|> put_status(200)
|> render(:smart_contract, %{address: address})
@@ -61,7 +61,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
def methods_read(conn, %{"address_hash" => address_hash_string} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- smart_contract <- Chain.address_hash_to_smart_contract(address_hash, @api_true),
+ smart_contract <- SmartContract.address_hash_to_smart_contract(address_hash, @api_true),
{:not_found, false} <- {:not_found, is_nil(smart_contract)} do
read_only_functions_from_abi = Reader.read_only_functions(smart_contract, address_hash, params["from"])
@@ -82,7 +82,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
{:not_found, true} <- {:not_found, AddressView.check_custom_abi_for_having_write_functions(custom_abi)} do
conn
|> put_status(200)
- |> json(Writer.filter_write_functions(custom_abi.abi))
+ |> json(custom_abi.abi |> Writer.filter_write_functions() |> Reader.get_abi_with_method_id())
end
end
@@ -91,11 +91,11 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
{:contract_interaction_disabled, AddressView.contract_interaction_disabled?()},
{:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- smart_contract <- Chain.address_hash_to_smart_contract(address_hash, @api_true),
+ smart_contract <- SmartContract.address_hash_to_smart_contract(address_hash, @api_true),
{:not_found, false} <- {:not_found, is_nil(smart_contract)} do
conn
|> put_status(200)
- |> json(Writer.write_functions(smart_contract))
+ |> json(smart_contract |> Writer.write_functions() |> Reader.get_abi_with_method_id())
end
end
@@ -109,7 +109,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
address.smart_contract
|> SmartContract.get_implementation_address_hash(@api_true)
|> Tuple.to_list()
- |> List.first() || @burn_address
+ |> List.first() || burn_address_hash_string()
conn
|> put_status(200)
@@ -131,11 +131,15 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
address.smart_contract
|> SmartContract.get_implementation_address_hash(@api_true)
|> Tuple.to_list()
- |> List.first() || @burn_address
+ |> List.first() || burn_address_hash_string()
conn
|> put_status(200)
- |> json(Writer.write_functions_proxy(implementation_address_hash_string, @api_true))
+ |> json(
+ implementation_address_hash_string
+ |> Writer.write_functions_proxy(@api_true)
+ |> Reader.get_abi_with_method_id()
+ )
end
end
@@ -176,6 +180,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
contract_type,
params["from"],
address.smart_contract.abi,
+ true,
@api_true
)
end
@@ -186,21 +191,44 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
end
end
+ @doc """
+ /api/v2/smart-contracts/${address_hash_string}/solidityscan-report logic
+ """
+ @spec solidityscan_report(Plug.Conn.t(), map()) ::
+ {:address, {:error, :not_found}}
+ | {:format_address, :error}
+ | {:is_empty_response, true}
+ | {:is_smart_contract, false | nil}
+ | {:restricted_access, true}
+ | Plug.Conn.t()
+ def solidityscan_report(conn, %{"address_hash" => address_hash_string} = params) do
+ with {:format_address, {:ok, address_hash}} <- {:format_address, Chain.string_to_address_hash(address_hash_string)},
+ {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
+ {:address, {:ok, address}} <- {:address, Chain.hash_to_address(address_hash)},
+ {:is_smart_contract, true} <- {:is_smart_contract, Address.is_smart_contract(address)},
+ response = SolidityScan.solidityscan_request(address_hash_string),
+ {:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do
+ conn
+ |> put_status(200)
+ |> json(response)
+ end
+ end
+
def smart_contracts_list(conn, params) do
full_options =
- [necessity_by_association: %{[address: :token] => :optional, [address: :names] => :optional}]
+ [necessity_by_association: %{[address: :token] => :optional, [address: :names] => :optional, address: :required}]
|> Keyword.merge(paging_options(params))
|> Keyword.merge(current_filter(params))
|> Keyword.merge(search_query(params))
+ |> Keyword.merge(smart_contracts_sorting(params))
|> Keyword.merge(@api_true)
- smart_contracts_plus_one = Chain.verified_contracts(full_options)
+ smart_contracts_plus_one = SmartContract.verified_contracts(full_options)
{smart_contracts, next_page} = split_list_by_page(smart_contracts_plus_one)
next_page_params =
next_page
- |> next_page_params(smart_contracts, params)
- |> delete_parameters_from_next_page_params()
+ |> next_page_params(smart_contracts, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex
index 20c93f33bbca..451275586b28 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex
@@ -2,14 +2,17 @@ defmodule BlockScoutWeb.API.V2.StatsController do
use Phoenix.Controller
alias BlockScoutWeb.API.V2.Helper
+ alias BlockScoutWeb.Chain.MarketHistoryChartController
+ alias EthereumJSONRPC.Variant
alias Explorer.{Chain, Market}
+ alias Explorer.Chain.Address.Counters
alias Explorer.Chain.Cache.Block, as: BlockCache
- alias Explorer.Chain.Cache.{GasPriceOracle, GasUsage}
+ alias Explorer.Chain.Cache.{GasPriceOracle, GasUsage, RootstockLockedBTC}
alias Explorer.Chain.Cache.Transaction, as: TransactionCache
alias Explorer.Chain.Supply.RSK
alias Explorer.Chain.Transaction.History.TransactionStats
alias Explorer.Counters.AverageBlockTime
- alias Explorer.ExchangeRates.Token
+ alias Plug.Conn
alias Timex.Duration
@api_true [api?: true]
@@ -24,7 +27,7 @@ defmodule BlockScoutWeb.API.V2.StatsController do
:standard
end
- exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null()
+ exchange_rate_from_db = Market.get_coin_exchange_rate()
transaction_stats = Helper.get_transaction_stats()
@@ -37,24 +40,45 @@ defmodule BlockScoutWeb.API.V2.StatsController do
nil
end
+ coin_price_change =
+ case Market.fetch_recent_history() do
+ [today, yesterday | _] ->
+ today.closing_price && yesterday.closing_price &&
+ today.closing_price
+ |> Decimal.div(yesterday.closing_price)
+ |> Decimal.sub(1)
+ |> Decimal.mult(100)
+ |> Decimal.to_float()
+ |> Float.ceil(2)
+
+ _ ->
+ nil
+ end
+
gas_price = Application.get_env(:block_scout_web, :gas_price)
json(
conn,
%{
"total_blocks" => BlockCache.estimated_count() |> to_string(),
- "total_addresses" => @api_true |> Chain.address_estimated_count() |> to_string(),
+ "total_addresses" => @api_true |> Counters.address_estimated_count() |> to_string(),
"total_transactions" => TransactionCache.estimated_count() |> to_string(),
"average_block_time" => AverageBlockTime.average_block_time() |> Duration.to_milliseconds(),
- "coin_price" => exchange_rate.usd_value,
+ "coin_price" => exchange_rate_from_db.usd_value,
+ "coin_price_change_percentage" => coin_price_change,
"total_gas_used" => GasUsage.total() |> to_string(),
"transactions_today" => Enum.at(transaction_stats, 0).number_of_transactions |> to_string(),
"gas_used_today" => Enum.at(transaction_stats, 0).gas_used,
"gas_prices" => gas_prices,
+ "gas_prices_update_in" => GasPriceOracle.update_in(),
+ "gas_price_updated_at" => GasPriceOracle.get_updated_at(),
"static_gas_price" => gas_price,
- "market_cap" => Helper.market_cap(market_cap_type, exchange_rate),
+ "market_cap" => Helper.market_cap(market_cap_type, exchange_rate_from_db),
+ "tvl" => exchange_rate_from_db.tvl_usd,
"network_utilization_percentage" => network_utilization_percentage()
}
+ |> add_rootstock_locked_btc()
+ |> backward_compatibility(conn)
)
end
@@ -91,28 +115,58 @@ defmodule BlockScoutWeb.API.V2.StatsController do
end
def market_chart(conn, _params) do
- exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null()
+ exchange_rate = Market.get_coin_exchange_rate()
recent_market_history = Market.fetch_recent_history()
+ current_total_supply = MarketHistoryChartController.available_supply(Chain.supply_for_days(), exchange_rate)
- market_history_data =
+ price_history_data =
recent_market_history
|> case do
[today | the_rest] ->
- [%{today | closing_price: exchange_rate.usd_value} | the_rest]
+ [
+ %{
+ today
+ | closing_price: exchange_rate.usd_value
+ }
+ | the_rest
+ ]
data ->
data
end
- |> Enum.map(fn day -> Map.take(day, [:closing_price, :date]) end)
+ |> Enum.map(fn day -> Map.take(day, [:closing_price, :market_cap, :tvl, :date]) end)
+
+ market_history_data =
+ MarketHistoryChartController.encode_market_history_data(price_history_data, current_total_supply)
json(conn, %{
chart_data: market_history_data,
- available_supply: available_supply(Chain.supply_for_days(), exchange_rate)
+ # todo: remove when new frontend is ready to use data from chart_data property only
+ available_supply: current_total_supply
})
end
- defp available_supply(:ok, exchange_rate), do: exchange_rate.available_supply || 0
+ defp add_rootstock_locked_btc(stats) do
+ with "rsk" <- Variant.get(),
+ rootstock_locked_btc when not is_nil(rootstock_locked_btc) <- RootstockLockedBTC.get_locked_value() do
+ stats |> Map.put("rootstock_locked_btc", rootstock_locked_btc)
+ else
+ _ -> stats
+ end
+ end
- defp available_supply({:ok, supply_for_days}, _exchange_rate), do: supply_for_days
+ defp backward_compatibility(response, conn) do
+ case Conn.get_req_header(conn, "updated-gas-oracle") do
+ ["true"] ->
+ response
+
+ _ ->
+ response
+ |> Map.update("gas_prices", nil, fn
+ gas_prices ->
+ %{slow: gas_prices[:slow][:price], average: gas_prices[:average][:price], fast: gas_prices[:fast][:price]}
+ end)
+ end
+ end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex
index 6655b0d40cb4..2e18b6649ec2 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex
@@ -2,8 +2,9 @@ defmodule BlockScoutWeb.API.V2.TokenController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.AccessHelper
- alias BlockScoutWeb.API.V2.TransactionView
- alias Explorer.Chain
+ alias BlockScoutWeb.API.V2.{AddressView, TransactionView}
+ alias Explorer.{Chain, Repo}
+ alias Explorer.Chain.{Address, Token, Token.Instance}
alias Indexer.Fetcher.TokenTotalSupplyOnDemand
import BlockScoutWeb.Chain,
@@ -17,13 +18,15 @@ defmodule BlockScoutWeb.API.V2.TokenController do
]
import BlockScoutWeb.PagingHelper,
- only: [delete_parameters_from_next_page_params: 1, token_transfers_types_options: 1]
+ only: [delete_parameters_from_next_page_params: 1, token_transfers_types_options: 1, tokens_sorting: 1]
+
+ import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1]
action_fallback(BlockScoutWeb.API.V2.FallbackController)
@api_true [api?: true]
- def token(conn, %{"address_hash" => address_hash_string} = params) do
+ def token(conn, %{"address_hash_param" => address_hash_string} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
{:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)} do
@@ -35,20 +38,20 @@ defmodule BlockScoutWeb.API.V2.TokenController do
end
end
- def counters(conn, %{"address_hash" => address_hash_string} = params) do
+ def counters(conn, %{"address_hash_param" => address_hash_string} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:not_found, {:ok, _}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)} do
+ {:not_found, true} <- {:not_found, Chain.token_from_address_hash_exists?(address_hash, @api_true)} do
{transfer_count, token_holder_count} = Chain.fetch_token_counters(address_hash, 30_000)
json(conn, %{transfers_count: to_string(transfer_count), token_holders_count: to_string(token_holder_count)})
end
end
- def transfers(conn, %{"address_hash" => address_hash_string} = params) do
+ def transfers(conn, %{"address_hash_param" => address_hash_string} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:not_found, {:ok, _}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)} do
+ {:not_found, true} <- {:not_found, Chain.token_from_address_hash_exists?(address_hash, @api_true)} do
paging_options = paging_options(params)
results =
@@ -61,17 +64,19 @@ defmodule BlockScoutWeb.API.V2.TokenController do
next_page_params =
next_page
- |> token_transfers_next_page_params(token_transfers, params)
- |> delete_parameters_from_next_page_params()
+ |> token_transfers_next_page_params(token_transfers, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
|> put_view(TransactionView)
- |> render(:token_transfers, %{token_transfers: token_transfers, next_page_params: next_page_params})
+ |> render(:token_transfers, %{
+ token_transfers: token_transfers |> maybe_preload_ens(),
+ next_page_params: next_page_params
+ })
end
end
- def holders(conn, %{"address_hash" => address_hash_string} = params) do
+ def holders(conn, %{"address_hash_param" => address_hash_string} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
{:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)} do
@@ -80,29 +85,70 @@ defmodule BlockScoutWeb.API.V2.TokenController do
{token_balances, next_page} = split_list_by_page(results_plus_one)
+ next_page_params = next_page |> next_page_params(token_balances, delete_parameters_from_next_page_params(params))
+
+ conn
+ |> put_status(200)
+ |> render(:token_balances, %{
+ token_balances: token_balances |> maybe_preload_ens(),
+ next_page_params: next_page_params,
+ token: token
+ })
+ end
+ end
+
+ def instances(
+ conn,
+ %{"address_hash_param" => address_hash_string, "holder_address_hash" => holder_address_hash_string} = params
+ ) do
+ with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
+ {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
+ {:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)},
+ {:not_found, false} <- {:not_found, Chain.is_erc_20_token?(token)},
+ {:format, {:ok, holder_address_hash}} <- {:format, Chain.string_to_address_hash(holder_address_hash_string)},
+ {:ok, false} <- AccessHelper.restricted_access?(holder_address_hash_string, params) do
+ holder_address = Repo.get_by(Address, hash: holder_address_hash)
+
+ results_plus_one =
+ Instance.token_instances_by_holder_address_hash(
+ token,
+ holder_address_hash,
+ params
+ |> unique_tokens_paging_options()
+ |> Keyword.merge(@api_true)
+ )
+
+ {token_instances, next_page} = split_list_by_page(results_plus_one)
+
next_page_params =
- next_page |> next_page_params(token_balances, params) |> delete_parameters_from_next_page_params()
+ next_page |> unique_tokens_next_page(token_instances, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
- |> render(:token_balances, %{token_balances: token_balances, next_page_params: next_page_params, token: token})
+ |> put_view(AddressView)
+ |> render(:nft_list, %{
+ token_instances: token_instances |> put_owner(holder_address),
+ next_page_params: next_page_params,
+ token: token
+ })
end
end
- def instances(conn, %{"address_hash" => address_hash_string} = params) do
+ def instances(conn, %{"address_hash_param" => address_hash_string} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
{:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)} do
results_plus_one =
Chain.address_to_unique_tokens(
token.contract_address_hash,
+ token,
Keyword.merge(unique_tokens_paging_options(params), @api_true)
)
{token_instances, next_page} = split_list_by_page(results_plus_one)
next_page_params =
- next_page |> unique_tokens_next_page(token_instances, params) |> delete_parameters_from_next_page_params()
+ next_page |> unique_tokens_next_page(token_instances, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
@@ -110,7 +156,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do
end
end
- def instance(conn, %{"address_hash" => address_hash_string, "token_id" => token_id_str} = params) do
+ def instance(conn, %{"address_hash_param" => address_hash_string, "token_id" => token_id_str} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
{:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)},
@@ -118,8 +164,13 @@ defmodule BlockScoutWeb.API.V2.TokenController do
{:format, {token_id, ""}} <- {:format, Integer.parse(token_id_str)} do
token_instance =
case Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, address_hash, @api_true) do
- {:ok, token_instance} -> token_instance |> Chain.put_owner_to_token_instance(@api_true)
- {:error, :not_found} -> %{token_id: token_id, metadata: nil, owner: nil}
+ {:ok, token_instance} ->
+ token_instance
+ |> Chain.select_repo(@api_true).preload(:owner)
+ |> Chain.put_owner_to_token_instance(token, @api_true)
+
+ {:error, :not_found} ->
+ %{token_id: token_id, metadata: nil, owner: nil}
end
conn
@@ -131,7 +182,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do
end
end
- def transfers_by_instance(conn, %{"address_hash" => address_hash_string, "token_id" => token_id_str} = params) do
+ def transfers_by_instance(conn, %{"address_hash_param" => address_hash_string, "token_id" => token_id_str} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
{:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)},
@@ -149,17 +200,19 @@ defmodule BlockScoutWeb.API.V2.TokenController do
next_page_params =
next_page
- |> token_transfers_next_page_params(token_transfers, params)
- |> delete_parameters_from_next_page_params()
+ |> token_transfers_next_page_params(token_transfers, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
|> put_view(TransactionView)
- |> render(:token_transfers, %{token_transfers: token_transfers, next_page_params: next_page_params})
+ |> render(:token_transfers, %{
+ token_transfers: token_transfers |> maybe_preload_ens(),
+ next_page_params: next_page_params
+ })
end
end
- def holders_by_instance(conn, %{"address_hash" => address_hash_string, "token_id" => token_id_str} = params) do
+ def holders_by_instance(conn, %{"address_hash_param" => address_hash_string, "token_id" => token_id_str} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
{:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)},
@@ -178,16 +231,22 @@ defmodule BlockScoutWeb.API.V2.TokenController do
next_page_params =
next_page
- |> next_page_params(token_holders, params)
- |> delete_parameters_from_next_page_params()
+ |> next_page_params(token_holders, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
- |> render(:token_balances, %{token_balances: token_holders, next_page_params: next_page_params, token: token})
+ |> render(:token_balances, %{
+ token_balances: token_holders |> maybe_preload_ens(),
+ next_page_params: next_page_params,
+ token: token
+ })
end
end
- def transfers_count_by_instance(conn, %{"address_hash" => address_hash_string, "token_id" => token_id_str} = params) do
+ def transfers_count_by_instance(
+ conn,
+ %{"address_hash_param" => address_hash_string, "token_id" => token_id_str} = params
+ ) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
{:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)},
@@ -208,14 +267,18 @@ defmodule BlockScoutWeb.API.V2.TokenController do
params
|> paging_options()
|> Keyword.merge(token_transfers_types_options(params))
+ |> Keyword.merge(tokens_sorting(params))
|> Keyword.merge(@api_true)
- {tokens, next_page} = filter |> Chain.list_top_tokens(options) |> split_list_by_page()
+ {tokens, next_page} = filter |> Token.list_top(options) |> split_list_by_page()
- next_page_params = next_page |> next_page_params(tokens, params) |> delete_parameters_from_next_page_params()
+ next_page_params = next_page |> next_page_params(tokens, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
|> render(:tokens, %{tokens: tokens, next_page_params: next_page_params})
end
+
+ defp put_owner(token_instances, holder_address),
+ do: Enum.map(token_instances, fn token_instance -> %Instance{token_instance | owner: holder_address} end)
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex
index 6e32354622aa..ff62beb9f5d8 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex
@@ -4,7 +4,13 @@ defmodule BlockScoutWeb.API.V2.TransactionController do
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Chain,
- only: [next_page_params: 3, token_transfers_next_page_params: 3, paging_options: 1, split_list_by_page: 1]
+ only: [
+ next_page_params: 3,
+ put_key_value_to_paging_options: 3,
+ token_transfers_next_page_params: 3,
+ paging_options: 1,
+ split_list_by_page: 1
+ ]
import BlockScoutWeb.PagingHelper,
only: [
@@ -16,9 +22,14 @@ defmodule BlockScoutWeb.API.V2.TransactionController do
type_filter_options: 1
]
+ import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1, maybe_preload_ens_to_transaction: 1]
+
alias BlockScoutWeb.AccessHelper
+ alias BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation, as: TransactionInterpretationService
alias BlockScoutWeb.Models.TransactionStateHelper
alias Explorer.Chain
+ alias Explorer.Chain.{Hash, Transaction}
+ alias Explorer.Chain.Zkevm.Reader
alias Indexer.Fetcher.FirstTraceOnDemand
action_fallback(BlockScoutWeb.API.V2.FallbackController)
@@ -36,9 +47,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do
[from_address: :smart_contract] => :optional,
[to_address: :smart_contract] => :optional,
[from_address: :names] => :optional,
- [to_address: :names] => :optional,
- from_address: :required,
- to_address: :required
+ [to_address: :names] => :optional
}
@token_transfers_in_tx_necessity_by_association %{
@@ -46,8 +55,6 @@ defmodule BlockScoutWeb.API.V2.TransactionController do
[to_address: :smart_contract] => :optional,
[from_address: :names] => :optional,
[to_address: :names] => :optional,
- from_address: :required,
- to_address: :required,
token: :required
}
@@ -56,7 +63,6 @@ defmodule BlockScoutWeb.API.V2.TransactionController do
[created_contract_address: :names] => :optional,
[from_address: :names] => :optional,
[to_address: :names] => :optional,
- [transaction: :block] => :optional,
[created_contract_address: :smart_contract] => :optional,
[from_address: :smart_contract] => :optional,
[to_address: :smart_contract] => :optional
@@ -65,25 +71,49 @@ defmodule BlockScoutWeb.API.V2.TransactionController do
@api_true [api?: true]
- def transaction(conn, %{"transaction_hash" => transaction_hash_string} = params) do
- with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)},
- {:not_found, {:ok, transaction}} <-
- {:not_found,
- Chain.hash_to_transaction(
- transaction_hash,
- necessity_by_association: Map.put(@transaction_necessity_by_association, :transaction_actions, :optional),
- api?: true
- )},
- {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params),
- {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params),
+ @doc """
+ Function to handle GET requests to `/api/v2/transactions/:transaction_hash_param` endpoint.
+ """
+ @spec transaction(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()}
+ def transaction(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do
+ necessity_by_association_with_actions =
+ Map.put(@transaction_necessity_by_association, :transaction_actions, :optional)
+
+ necessity_by_association =
+ case Application.get_env(:explorer, :chain_type) do
+ "polygon_zkevm" ->
+ necessity_by_association_with_actions
+ |> Map.put(:zkevm_batch, :optional)
+ |> Map.put(:zkevm_sequence_transaction, :optional)
+ |> Map.put(:zkevm_verify_transaction, :optional)
+
+ "suave" ->
+ necessity_by_association_with_actions
+ |> Map.put(:logs, :optional)
+ |> Map.put([execution_node: :names], :optional)
+ |> Map.put([wrapped_to_address: :names], :optional)
+
+ _ ->
+ necessity_by_association_with_actions
+ end
+
+ with {:ok, transaction, _transaction_hash} <-
+ validate_transaction(transaction_hash_string, params,
+ necessity_by_association: necessity_by_association,
+ api?: true
+ ),
preloaded <-
Chain.preload_token_transfers(transaction, @token_transfers_in_tx_necessity_by_association, @api_true, false) do
conn
|> put_status(200)
- |> render(:transaction, %{transaction: preloaded})
+ |> render(:transaction, %{transaction: preloaded |> maybe_preload_ens_to_transaction()})
end
end
+ @doc """
+ Function to handle GET requests to `/api/v2/transactions` endpoint.
+ """
+ @spec transactions(Plug.Conn.t(), map()) :: Plug.Conn.t()
def transactions(conn, params) do
filter_options = filter_options(params, :validated)
@@ -100,19 +130,57 @@ defmodule BlockScoutWeb.API.V2.TransactionController do
{transactions, next_page} = split_list_by_page(transactions_plus_one)
- next_page_params = next_page |> next_page_params(transactions, params) |> delete_parameters_from_next_page_params()
+ next_page_params = next_page |> next_page_params(transactions, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
- |> render(:transactions, %{transactions: transactions, next_page_params: next_page_params})
+ |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), next_page_params: next_page_params})
end
- def raw_trace(conn, %{"transaction_hash" => transaction_hash_string} = params) do
- with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)},
- {:not_found, {:ok, transaction}} <-
- {:not_found, Chain.hash_to_transaction(transaction_hash, @api_true)},
- {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params),
- {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do
+ @doc """
+ Function to handle GET requests to `/api/v2/transactions/zkevm-batch/:batch_number` endpoint.
+ It renders the list of L2 transactions bound to the specified batch.
+ """
+ @spec zkevm_batch(Plug.Conn.t(), map()) :: Plug.Conn.t()
+ def zkevm_batch(conn, %{"batch_number" => batch_number} = _params) do
+ transactions =
+ batch_number
+ |> Reader.batch_transactions(api?: true)
+ |> Enum.map(fn tx -> tx.hash end)
+ |> Chain.hashes_to_transactions(api?: true, necessity_by_association: @transaction_necessity_by_association)
+
+ conn
+ |> put_status(200)
+ |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), items: true})
+ end
+
+ def execution_node(conn, %{"execution_node_hash_param" => execution_node_hash_string} = params) do
+ with {:format, {:ok, execution_node_hash}} <- {:format, Chain.string_to_address_hash(execution_node_hash_string)} do
+ full_options =
+ [necessity_by_association: @transaction_necessity_by_association]
+ |> Keyword.merge(put_key_value_to_paging_options(paging_options(params), :is_index_in_asc_order, true))
+ |> Keyword.merge(@api_true)
+
+ transactions_plus_one = Chain.execution_node_to_transactions(execution_node_hash, full_options)
+
+ {transactions, next_page} = split_list_by_page(transactions_plus_one)
+
+ next_page_params =
+ next_page
+ |> next_page_params(transactions, delete_parameters_from_next_page_params(params))
+
+ conn
+ |> put_status(200)
+ |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), next_page_params: next_page_params})
+ end
+ end
+
+ @doc """
+ Function to handle GET requests to `/api/v2/transactions/:transaction_hash_param/raw-trace` endpoint.
+ """
+ @spec raw_trace(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()}
+ def raw_trace(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do
+ with {:ok, transaction, transaction_hash} <- validate_transaction(transaction_hash_string, params) do
if is_nil(transaction.block_number) do
conn
|> put_status(200)
@@ -136,12 +204,12 @@ defmodule BlockScoutWeb.API.V2.TransactionController do
end
end
- def token_transfers(conn, %{"transaction_hash" => transaction_hash_string} = params) do
- with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)},
- {:not_found, {:ok, transaction}} <-
- {:not_found, Chain.hash_to_transaction(transaction_hash, @api_true)},
- {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params),
- {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do
+ @doc """
+ Function to handle GET requests to `/api/v2/transactions/:transaction_hash_param/token-transfers` endpoint.
+ """
+ @spec token_transfers(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()}
+ def token_transfers(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do
+ with {:ok, _transaction, transaction_hash} <- validate_transaction(transaction_hash_string, params) do
paging_options = paging_options(params)
full_options =
@@ -160,21 +228,23 @@ defmodule BlockScoutWeb.API.V2.TransactionController do
next_page_params =
next_page
- |> token_transfers_next_page_params(token_transfers, params)
- |> delete_parameters_from_next_page_params()
+ |> token_transfers_next_page_params(token_transfers, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
- |> render(:token_transfers, %{token_transfers: token_transfers, next_page_params: next_page_params})
+ |> render(:token_transfers, %{
+ token_transfers: token_transfers |> maybe_preload_ens(),
+ next_page_params: next_page_params
+ })
end
end
- def internal_transactions(conn, %{"transaction_hash" => transaction_hash_string} = params) do
- with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)},
- {:not_found, {:ok, transaction}} <-
- {:not_found, Chain.hash_to_transaction(transaction_hash, @api_true)},
- {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params),
- {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do
+ @doc """
+ Function to handle GET requests to `/api/v2/transactions/:transaction_hash_param/internal-transactions` endpoint.
+ """
+ @spec internal_transactions(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()}
+ def internal_transactions(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do
+ with {:ok, _transaction, transaction_hash} <- validate_transaction(transaction_hash_string, params) do
full_options =
@internal_transaction_necessity_by_association
|> Keyword.merge(paging_options(params))
@@ -186,24 +256,23 @@ defmodule BlockScoutWeb.API.V2.TransactionController do
next_page_params =
next_page
- |> next_page_params(internal_transactions, params)
- |> delete_parameters_from_next_page_params()
+ |> next_page_params(internal_transactions, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
|> render(:internal_transactions, %{
- internal_transactions: internal_transactions,
+ internal_transactions: internal_transactions |> maybe_preload_ens(),
next_page_params: next_page_params
})
end
end
- def logs(conn, %{"transaction_hash" => transaction_hash_string} = params) do
- with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)},
- {:not_found, {:ok, transaction}} <-
- {:not_found, Chain.hash_to_transaction(transaction_hash, @api_true)},
- {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params),
- {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do
+ @doc """
+ Function to handle GET requests to `/api/v2/transactions/:transaction_hash_param/logs` endpoint.
+ """
+ @spec logs(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()}
+ def logs(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do
+ with {:ok, _transaction, transaction_hash} <- validate_transaction(transaction_hash_string, params) do
full_options =
[
necessity_by_association: %{
@@ -221,38 +290,48 @@ defmodule BlockScoutWeb.API.V2.TransactionController do
next_page_params =
next_page
- |> next_page_params(logs, params)
- |> delete_parameters_from_next_page_params()
+ |> next_page_params(logs, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
|> render(:logs, %{
tx_hash: transaction_hash,
- logs: logs,
+ logs: logs |> maybe_preload_ens(),
next_page_params: next_page_params
})
end
end
- def state_changes(conn, %{"transaction_hash" => transaction_hash_string} = params) do
- with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)},
- {:not_found, {:ok, transaction}} <-
- {:not_found,
- Chain.hash_to_transaction(transaction_hash,
- necessity_by_association:
- Map.merge(@transaction_necessity_by_association, %{[block: [miner: :names]] => :optional}),
- api?: true
- )},
- {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params),
- {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do
- state_changes = TransactionStateHelper.state_changes(transaction)
+ @doc """
+ Function to handle GET requests to `/api/v2/transactions/:transaction_hash_param/state-changes` endpoint.
+ """
+ @spec state_changes(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()}
+ def state_changes(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do
+ with {:ok, transaction, _transaction_hash} <-
+ validate_transaction(transaction_hash_string, params,
+ necessity_by_association:
+ Map.merge(@transaction_necessity_by_association, %{[block: [miner: :names]] => :optional}),
+ api?: true
+ ) do
+ state_changes_plus_next_page =
+ transaction |> TransactionStateHelper.state_changes(params |> paging_options() |> Keyword.merge(api?: true))
+
+ {state_changes, next_page} = split_list_by_page(state_changes_plus_next_page)
+
+ next_page_params =
+ next_page
+ |> next_page_params(state_changes, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
- |> render(:state_changes, %{state_changes: state_changes})
+ |> render(:state_changes, %{state_changes: state_changes, next_page_params: next_page_params})
end
end
+ @doc """
+ Function to handle GET requests to `/api/v2/transactions/watchlist` endpoint.
+ """
+ @spec watchlist_transactions(Plug.Conn.t(), map()) :: Plug.Conn.t() | {:auth, any()}
def watchlist_transactions(conn, params) do
with {:auth, %{watchlist_id: watchlist_id}} <- {:auth, current_user(conn)} do
full_options =
@@ -266,16 +345,65 @@ defmodule BlockScoutWeb.API.V2.TransactionController do
{transactions, next_page} = split_list_by_page(transactions_plus_one)
- next_page_params =
- next_page |> next_page_params(transactions, params) |> delete_parameters_from_next_page_params()
+ next_page_params = next_page |> next_page_params(transactions, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
|> render(:transactions_watchlist, %{
- transactions: transactions,
+ transactions: transactions |> maybe_preload_ens(),
next_page_params: next_page_params,
watchlist_names: watchlist_names
})
end
end
+
+ def summary(conn, %{"transaction_hash_param" => transaction_hash_string, "just_request_body" => "true"} = params) do
+ with {:tx_interpreter_enabled, true} <- {:tx_interpreter_enabled, TransactionInterpretationService.enabled?()},
+ {:ok, transaction, _transaction_hash} <- validate_transaction(transaction_hash_string, params) do
+ conn
+ |> json(TransactionInterpretationService.get_request_body(transaction))
+ end
+ end
+
+ @doc """
+ Function to handle GET requests to `/api/v2/transactions/:transaction_hash_param/summary` endpoint.
+ """
+ @spec summary(Plug.Conn.t(), map()) ::
+ {:format, :error}
+ | {:not_found, {:error, :not_found}}
+ | {:restricted_access, true}
+ | {:tx_interpreter_enabled, boolean}
+ | Plug.Conn.t()
+ def summary(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do
+ with {:tx_interpreter_enabled, true} <- {:tx_interpreter_enabled, TransactionInterpretationService.enabled?()},
+ {:ok, transaction, _transaction_hash} <- validate_transaction(transaction_hash_string, params) do
+ response =
+ case TransactionInterpretationService.interpret(transaction) do
+ {:ok, response} -> response
+ {:error, %Jason.DecodeError{}} -> %{error: "Error while tx interpreter response decoding"}
+ {:error, error} -> %{error: error}
+ end
+
+ conn
+ |> json(response)
+ end
+ end
+
+ @doc """
+ Checks if this valid transaction hash string, and this transaction doesn't belong to prohibited address
+ """
+ @spec validate_transaction(String.t(), any(), Keyword.t()) ::
+ {:format, :error}
+ | {:not_found, {:error, :not_found}}
+ | {:restricted_access, true}
+ | {:ok, Transaction.t(), Hash.t()}
+ def validate_transaction(transaction_hash_string, params, options \\ @api_true) do
+ with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)},
+ {:not_found, {:ok, transaction}} <-
+ {:not_found, Chain.hash_to_transaction(transaction_hash, options)},
+ {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params),
+ {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do
+ {:ok, transaction, transaction_hash}
+ end
+ end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex
index 60ee9fbc13f0..449630b14cad 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex
@@ -3,9 +3,12 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
import Explorer.SmartContract.Solidity.Verifier, only: [parse_boolean: 1]
+ require Logger
+
alias BlockScoutWeb.AccessHelper
alias BlockScoutWeb.API.V2.ApiView
alias Explorer.Chain
+ alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.Solidity.PublisherWorker, as: SolidityPublisherWorker
alias Explorer.SmartContract.Solidity.PublishHelper
alias Explorer.SmartContract.Vyper.PublisherWorker, as: VyperPublisherWorker
@@ -14,9 +17,9 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
action_fallback(BlockScoutWeb.API.V2.FallbackController)
@api_true [api?: true]
+ @sc_verification_started "Smart-contract verification started"
def config(conn, _params) do
- evm_versions = CodeCompiler.allowed_evm_versions()
solidity_compiler_versions = CompilerVersion.fetch_version_list(:solc)
vyper_compiler_versions = CompilerVersion.fetch_version_list(:vyper)
@@ -26,16 +29,18 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
do: ["sourcify" | &1],
else: &1
)).()
- |> (&if(RustVerifierInterface.enabled?(), do: ["multi-part" | &1], else: &1)).()
- |> (&if(RustVerifierInterface.enabled?(), do: ["vyper-multi-part" | &1], else: &1)).()
+ |> (&if(RustVerifierInterface.enabled?(),
+ do: ["multi-part", "vyper-multi-part", "vyper-standard-input"] ++ &1,
+ else: &1
+ )).()
conn
|> json(%{
- solidity_evm_versions: evm_versions,
+ solidity_evm_versions: CodeCompiler.evm_versions(:solidity),
solidity_compiler_versions: solidity_compiler_versions,
vyper_compiler_versions: vyper_compiler_versions,
verification_options: verification_options,
- vyper_evm_versions: ["byzantium", "constantinople", "petersburg", "istanbul"],
+ vyper_evm_versions: CodeCompiler.evm_versions(:vyper),
is_rust_verifier_microservice_enabled: RustVerifierInterface.enabled?()
})
end
@@ -45,10 +50,9 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
%{"address_hash" => address_hash_string, "compiler_version" => compiler_version, "source_code" => source_code} =
params
) do
- with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
- {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:already_verified, false} <-
- {:already_verified, Chain.smart_contract_fully_verified?(address_hash, @api_true)} do
+ Logger.info("API v2 smart-contract #{address_hash_string} verification via flattened file")
+
+ with :validated <- validate_address(params) do
verification_params =
%{
"address_hash" => String.downcase(address_hash_string),
@@ -67,27 +71,22 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
|> Map.put("external_libraries", Map.get(params, "libraries", %{}))
|> Map.put("is_yul", Map.get(params, "is_yul_contract", false))
+ log_sc_verification_started(address_hash_string)
Que.add(SolidityPublisherWorker, {"flattened_api_v2", verification_params})
conn
|> put_view(ApiView)
- |> render(:message, %{message: "Verification started"})
+ |> render(:message, %{message: @sc_verification_started})
end
end
- # sobelow_skip ["Traversal.FileModule"]
def verification_via_standard_input(
conn,
- %{"address_hash" => address_hash_string, "files" => files, "compiler_version" => compiler_version} = params
+ %{"address_hash" => address_hash_string, "files" => _files, "compiler_version" => compiler_version} = params
) do
- with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
- {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:already_verified, false} <-
- {:already_verified, Chain.smart_contract_fully_verified?(address_hash, @api_true)},
- files_array <- PublishHelper.prepare_files_array(files),
- {:no_json_file, %Plug.Upload{path: path}} <-
- {:no_json_file, PublishHelper.get_one_json(files_array)},
- {:file_error, {:ok, json_input}} <- {:file_error, File.read(path)} do
+ Logger.info("API v2 smart-contract #{address_hash_string} verification via standard json input")
+
+ with {:json_input, json_input} <- validate_params_standard_json_input(params) do
verification_params =
%{
"address_hash" => String.downcase(address_hash_string),
@@ -97,27 +96,29 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
|> Map.put("constructor_arguments", Map.get(params, "constructor_args", ""))
|> Map.put("name", Map.get(params, "contract_name", ""))
+ log_sc_verification_started(address_hash_string)
Que.add(SolidityPublisherWorker, {"json_api_v2", verification_params, json_input})
conn
|> put_view(ApiView)
- |> render(:message, %{message: "Verification started"})
+ |> render(:message, %{message: @sc_verification_started})
end
end
def verification_via_sourcify(conn, %{"address_hash" => address_hash_string, "files" => files} = params) do
+ Logger.info("API v2 smart-contract #{address_hash_string} verification via Sourcify")
+
with {:not_found, true} <-
{:not_found, Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled]},
- {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
- {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:already_verified, false} <-
- {:already_verified, Chain.smart_contract_fully_verified?(address_hash, @api_true)},
+ :validated <- validate_address(params),
files_array <- PublishHelper.prepare_files_array(files),
{:no_json_file, %Plug.Upload{path: _path}} <-
{:no_json_file, PublishHelper.get_one_json(files_array)},
files_content <- PublishHelper.read_files(files_array) do
chosen_contract = params["chosen_contract_index"]
+ log_sc_verification_started(address_hash_string)
+
Que.add(
SolidityPublisherWorker,
{"sourcify_api_v2", String.downcase(address_hash_string), files_content, conn, chosen_contract}
@@ -125,7 +126,7 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
conn
|> put_view(ApiView)
- |> render(:message, %{message: "Verification started"})
+ |> render(:message, %{message: @sc_verification_started})
end
end
@@ -133,11 +134,10 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
conn,
%{"address_hash" => address_hash_string, "compiler_version" => compiler_version, "files" => files} = params
) do
- with {:not_found, true} <- {:not_found, RustVerifierInterface.enabled?()},
- {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
- {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:already_verified, false} <-
- {:already_verified, Chain.smart_contract_fully_verified?(address_hash, @api_true)},
+ Logger.info("API v2 smart-contract #{address_hash_string} verification via multipart")
+
+ with :verifier_enabled <- check_microservice(),
+ :validated <- validate_address(params),
libraries <- Map.get(params, "libraries", "{}"),
{:libs_format, {:ok, json}} <- {:libs_format, Jason.decode(libraries)} do
verification_params =
@@ -155,14 +155,15 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
files_array =
files
- |> Map.values()
+ |> PublishHelper.prepare_files_array()
|> PublishHelper.read_files()
+ log_sc_verification_started(address_hash_string)
Que.add(SolidityPublisherWorker, {"multipart_api_v2", verification_params, files_array})
conn
|> put_view(ApiView)
- |> render(:message, %{message: "Verification started"})
+ |> render(:message, %{message: @sc_verification_started})
end
end
@@ -171,10 +172,7 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
%{"address_hash" => address_hash_string, "compiler_version" => compiler_version, "source_code" => source_code} =
params
) do
- with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
- {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:already_verified, false} <-
- {:already_verified, Chain.smart_contract_fully_verified?(address_hash, @api_true)} do
+ with :validated <- validate_address(params) do
verification_params =
%{
"address_hash" => String.downcase(address_hash_string),
@@ -183,13 +181,14 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
}
|> Map.put("constructor_arguments", Map.get(params, "constructor_args", "") || "")
|> Map.put("name", Map.get(params, "contract_name", "Vyper_contract"))
- |> Map.put("evm_version", Map.get(params, "evm_version", "istanbul"))
+ |> Map.put("evm_version", Map.get(params, "evm_version"))
- Que.add(VyperPublisherWorker, {address_hash_string, verification_params})
+ log_sc_verification_started(address_hash_string)
+ Que.add(VyperPublisherWorker, {"vyper_flattened", verification_params})
conn
|> put_view(ApiView)
- |> render(:message, %{message: "Verification started"})
+ |> render(:message, %{message: @sc_verification_started})
end
end
@@ -197,28 +196,105 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
conn,
%{"address_hash" => address_hash_string, "compiler_version" => compiler_version, "files" => files} = params
) do
- with {:not_found, true} <- {:not_found, RustVerifierInterface.enabled?()},
- {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
- {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- {:already_verified, false} <-
- {:already_verified, Chain.smart_contract_fully_verified?(address_hash, @api_true)} do
+ Logger.info("API v2 vyper smart-contract #{address_hash_string} verification")
+
+ with :verifier_enabled <- check_microservice(),
+ :validated <- validate_address(params) do
+ interfaces = parse_interfaces(params["interfaces"])
+
verification_params =
%{
"address_hash" => String.downcase(address_hash_string),
"compiler_version" => compiler_version
}
- |> Map.put("evm_version", Map.get(params, "evm_version", "istanbul"))
+ |> Map.put("evm_version", Map.get(params, "evm_version"))
+ |> Map.put("interfaces", interfaces)
files_array =
files
- |> Map.values()
+ |> PublishHelper.prepare_files_array()
|> PublishHelper.read_files()
- Que.add(VyperPublisherWorker, {address_hash_string, verification_params, files_array})
+ log_sc_verification_started(address_hash_string)
+ Que.add(VyperPublisherWorker, {"vyper_multipart", verification_params, files_array})
+
+ conn
+ |> put_view(ApiView)
+ |> render(:message, %{message: @sc_verification_started})
+ end
+ end
+
+ def verification_via_vyper_standard_input(
+ conn,
+ %{"address_hash" => address_hash_string, "files" => _files, "compiler_version" => compiler_version} = params
+ ) do
+ Logger.info("API v2 vyper smart-contract #{address_hash_string} verification via standard json input")
+
+ with :verifier_enabled <- check_microservice(),
+ {:json_input, json_input} <- validate_params_standard_json_input(params) do
+ verification_params = %{
+ "address_hash" => String.downcase(address_hash_string),
+ "compiler_version" => compiler_version,
+ "input" => json_input
+ }
+
+ log_sc_verification_started(address_hash_string)
+ Que.add(VyperPublisherWorker, {"vyper_standard_json", verification_params})
conn
|> put_view(ApiView)
- |> render(:message, %{message: "Verification started"})
+ |> render(:message, %{message: @sc_verification_started})
end
end
+
+ defp parse_interfaces(interfaces) do
+ cond do
+ is_binary(interfaces) ->
+ case Jason.decode(interfaces) do
+ {:ok, map} ->
+ map
+
+ _ ->
+ nil
+ end
+
+ is_map(interfaces) ->
+ interfaces
+ |> PublishHelper.prepare_files_array()
+ |> PublishHelper.read_files()
+
+ true ->
+ nil
+ end
+ end
+
+ # sobelow_skip ["Traversal.FileModule"]
+ defp validate_params_standard_json_input(%{"files" => files} = params) do
+ with :validated <- validate_address(params),
+ files_array <- PublishHelper.prepare_files_array(files),
+ {:no_json_file, %Plug.Upload{path: path}} <-
+ {:no_json_file, PublishHelper.get_one_json(files_array)},
+ {:file_error, {:ok, json_input}} <- {:file_error, File.read(path)} do
+ {:json_input, json_input}
+ end
+ end
+
+ defp validate_address(%{"address_hash" => address_hash_string} = params) do
+ with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
+ {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
+ {:already_verified, false} <-
+ {:already_verified, SmartContract.verified_with_full_match?(address_hash, @api_true)} do
+ :validated
+ end
+ end
+
+ defp check_microservice do
+ with {:not_found, true} <- {:not_found, RustVerifierInterface.enabled?()} do
+ :verifier_enabled
+ end
+ end
+
+ defp log_sc_verification_started(address_hash_string) do
+ Logger.info("API v2 smart-contract #{address_hash_string} verification request sent to the microservice")
+ end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/withdrawal_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/withdrawal_controller.ex
index 396e66712a54..4282d16d4fbb 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/withdrawal_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/withdrawal_controller.ex
@@ -5,6 +5,7 @@ defmodule BlockScoutWeb.API.V2.WithdrawalController do
only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
import BlockScoutWeb.PagingHelper, only: [delete_parameters_from_next_page_params: 1]
+ import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1]
alias Explorer.Chain
@@ -16,11 +17,11 @@ defmodule BlockScoutWeb.API.V2.WithdrawalController do
withdrawals_plus_one = Chain.list_withdrawals(full_options)
{withdrawals, next_page} = split_list_by_page(withdrawals_plus_one)
- next_page_params = next_page |> next_page_params(withdrawals, params) |> delete_parameters_from_next_page_params()
+ next_page_params = next_page |> next_page_params(withdrawals, delete_parameters_from_next_page_params(params))
conn
|> put_status(200)
- |> render(:withdrawals, %{withdrawals: withdrawals, next_page_params: next_page_params})
+ |> render(:withdrawals, %{withdrawals: withdrawals |> maybe_preload_ens(), next_page_params: next_page_params})
end
def withdrawals_counters(conn, _params) do
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/zkevm_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/zkevm_controller.ex
new file mode 100644
index 000000000000..cd45dab110b7
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/zkevm_controller.ex
@@ -0,0 +1,112 @@
+defmodule BlockScoutWeb.API.V2.ZkevmController do
+ use BlockScoutWeb, :controller
+
+ import BlockScoutWeb.Chain,
+ only: [
+ next_page_params: 3,
+ paging_options: 1,
+ split_list_by_page: 1
+ ]
+
+ alias Explorer.Chain.Zkevm.Reader
+
+ action_fallback(BlockScoutWeb.API.V2.FallbackController)
+
+ @batch_necessity_by_association %{
+ :sequence_transaction => :optional,
+ :verify_transaction => :optional,
+ :l2_transactions => :optional
+ }
+
+ @batches_necessity_by_association %{
+ :sequence_transaction => :optional,
+ :verify_transaction => :optional
+ }
+
+ @doc """
+ Function to handle GET requests to `/api/v2/zkevm/batches/:batch_number` endpoint.
+ """
+ @spec batch(Plug.Conn.t(), map()) :: Plug.Conn.t()
+ def batch(conn, %{"batch_number" => batch_number} = _params) do
+ case Reader.batch(
+ batch_number,
+ necessity_by_association: @batch_necessity_by_association,
+ api?: true
+ ) do
+ {:ok, batch} ->
+ conn
+ |> put_status(200)
+ |> render(:zkevm_batch, %{batch: batch})
+
+ {:error, :not_found} = res ->
+ res
+ end
+ end
+
+ @doc """
+ Function to handle GET requests to `/api/v2/main-page/zkevm/batches/latest-number` endpoint.
+ """
+ @spec batch_latest_number(Plug.Conn.t(), map()) :: Plug.Conn.t()
+ def batch_latest_number(conn, _params) do
+ conn
+ |> put_status(200)
+ |> render(:zkevm_batch_latest_number, %{number: batch_latest_number()})
+ end
+
+ @doc """
+ Function to handle GET requests to `/api/v2/zkevm/batches` endpoint.
+ """
+ @spec batches(Plug.Conn.t(), map()) :: Plug.Conn.t()
+ def batches(conn, params) do
+ {batches, next_page} =
+ params
+ |> paging_options()
+ |> Keyword.put(:necessity_by_association, @batches_necessity_by_association)
+ |> Keyword.put(:api?, true)
+ |> Reader.batches()
+ |> split_list_by_page()
+
+ next_page_params = next_page_params(next_page, batches, params)
+
+ conn
+ |> put_status(200)
+ |> render(:zkevm_batches, %{
+ batches: batches,
+ next_page_params: next_page_params
+ })
+ end
+
+ @doc """
+ Function to handle GET requests to `/api/v2/zkevm/batches/count` endpoint.
+ """
+ @spec batches_count(Plug.Conn.t(), map()) :: Plug.Conn.t()
+ def batches_count(conn, _params) do
+ conn
+ |> put_status(200)
+ |> render(:zkevm_batches_count, %{count: batch_latest_number()})
+ end
+
+ @doc """
+ Function to handle GET requests to `/api/v2/main-page/zkevm/batches/confirmed` endpoint.
+ """
+ @spec batches_confirmed(Plug.Conn.t(), map()) :: Plug.Conn.t()
+ def batches_confirmed(conn, _params) do
+ batches =
+ []
+ |> Keyword.put(:necessity_by_association, @batches_necessity_by_association)
+ |> Keyword.put(:api?, true)
+ |> Keyword.put(:confirmed?, true)
+ |> Reader.batches()
+
+ conn
+ |> put_status(200)
+ |> render(:zkevm_batches, %{batches: batches})
+ end
+
+ defp batch_latest_number do
+ case Reader.batch(:latest, api?: true) do
+ {:ok, batch} -> batch.number
+ {:error, :not_found} -> 0
+ end
+ end
+end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex
index 74ef3f6b2f1e..4a92e7c5ca52 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex
@@ -11,12 +11,13 @@ defmodule BlockScoutWeb.BlockTransactionController do
]
import Explorer.Chain, only: [hash_to_block: 2, number_to_block: 2]
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
alias BlockScoutWeb.{Controller, TransactionView}
alias Explorer.Chain
alias Phoenix.View
- {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
+ {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string())
@burn_address_hash burn_address_hash
def index(conn, %{"block_hash_or_number" => formatted_block_hash_or_number, "type" => "JSON"} = params) do
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/chain/market_history_chart_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/chain/market_history_chart_controller.ex
index 238f858769f7..194d865e2252 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/chain/market_history_chart_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain/market_history_chart_controller.ex
@@ -2,51 +2,68 @@ defmodule BlockScoutWeb.Chain.MarketHistoryChartController do
use BlockScoutWeb, :controller
alias Explorer.{Chain, Market}
- alias Explorer.ExchangeRates.Token
def show(conn, _params) do
if ajax?(conn) do
- exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null()
+ exchange_rate = Market.get_coin_exchange_rate()
recent_market_history = Market.fetch_recent_history()
+ current_total_supply = available_supply(Chain.supply_for_days(), exchange_rate)
- market_history_data =
+ price_history_data =
case recent_market_history do
[today | the_rest] ->
- encode_market_history_data([%{today | closing_price: exchange_rate.usd_value} | the_rest])
+ [%{today | closing_price: exchange_rate.usd_value} | the_rest]
data ->
- encode_market_history_data(data)
+ data
end
+ market_history_data = encode_market_history_data(price_history_data, current_total_supply)
+
json(conn, %{
- history_data: market_history_data,
- supply_data: available_supply(Chain.supply_for_days(), exchange_rate)
+ history_data: market_history_data
})
else
unprocessable_entity(conn)
end
end
- defp available_supply(:ok, exchange_rate) do
- to_string(exchange_rate.available_supply || 0)
- end
+ def available_supply(:ok, exchange_rate), do: exchange_rate.available_supply || 0
- defp available_supply({:ok, supply_for_days}, _exchange_rate) do
+ def available_supply({:ok, supply_for_days}, _exchange_rate) do
supply_for_days
|> Jason.encode()
|> case do
- {:ok, data} -> data
- _ -> []
+ {:ok, _data} ->
+ current_date =
+ supply_for_days
+ |> Map.keys()
+ |> Enum.max(Date)
+
+ Map.get(supply_for_days, current_date)
+
+ _ ->
+ nil
end
end
- defp encode_market_history_data(market_history_data) do
+ def encode_market_history_data(market_history_data, current_total_supply) when is_binary(current_total_supply) do
+ encode_market_history_data(market_history_data, Decimal.new(current_total_supply))
+ end
+
+ def encode_market_history_data(market_history_data, current_total_supply) do
market_history_data
- |> Enum.map(fn day -> Map.take(day, [:closing_price, :date]) end)
+ |> Enum.map(fn day ->
+ market_cap = if day.market_cap, do: day.market_cap, else: Decimal.mult(current_total_supply, day.closing_price)
+
+ day
+ |> Map.put(:market_cap, market_cap)
+ |> Map.take([:closing_price, :market_cap, :tvl, :date])
+ end)
|> Jason.encode()
|> case do
- {:ok, data} -> data
+ {:ok, data} -> Jason.decode!(data)
_ -> []
end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex
index 95cd87fcf7e5..eabc844c7d01 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex
@@ -6,13 +6,14 @@ defmodule BlockScoutWeb.ChainController do
alias BlockScoutWeb.API.V2.Helper
alias BlockScoutWeb.{ChainView, Controller}
alias Explorer.{Chain, PagingOptions, Repo}
+ alias Explorer.Chain.Address.Counters
alias Explorer.Chain.{Address, Block, Transaction}
alias Explorer.Chain.Cache.Block, as: BlockCache
alias Explorer.Chain.Cache.GasUsage
alias Explorer.Chain.Cache.Transaction, as: TransactionCache
+ alias Explorer.Chain.Search
alias Explorer.Chain.Supply.RSK
alias Explorer.Counters.AverageBlockTime
- alias Explorer.ExchangeRates.Token
alias Explorer.Market
alias Phoenix.View
@@ -20,7 +21,7 @@ defmodule BlockScoutWeb.ChainController do
transaction_estimated_count = TransactionCache.estimated_count()
total_gas_usage = GasUsage.total()
block_count = BlockCache.estimated_count()
- address_count = Chain.address_estimated_count()
+ address_count = Counters.address_estimated_count()
market_cap_calculation =
case Application.get_env(:explorer, :supply) do
@@ -31,7 +32,7 @@ defmodule BlockScoutWeb.ChainController do
:standard
end
- exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null()
+ exchange_rate = Market.get_coin_exchange_rate()
transaction_stats = Helper.get_transaction_stats()
@@ -91,7 +92,7 @@ defmodule BlockScoutWeb.ChainController do
results =
paging_options
- |> Chain.joint_search(offset, term)
+ |> Search.joint_search(offset, term)
encoded_results =
results
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/csv_export_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/csv_export_controller.ex
index 08f05ba937d8..1cb332b2a49e 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/csv_export_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/csv_export_controller.ex
@@ -8,8 +8,15 @@ defmodule BlockScoutWeb.CsvExportController do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
:ok <- Chain.check_address_exists(address_hash),
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
- true <- supported_export_type(type) do
- render(conn, "index.html", address_hash_string: address_hash_string, type: type)
+ true <- supported_export_type(type),
+ filter_type <- Map.get(params, "filter_type"),
+ filter_value <- Map.get(params, "filter_value") do
+ render(conn, "index.html",
+ address_hash_string: address_hash_string,
+ type: type,
+ filter_type: filter_type && String.downcase(filter_type),
+ filter_value: filter_value && String.downcase(filter_value)
+ )
else
_ ->
not_found(conn)
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/pending_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/pending_transaction_controller.ex
index 906fbc3194b4..f709855b000a 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/pending_transaction_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/pending_transaction_controller.ex
@@ -2,12 +2,13 @@ defmodule BlockScoutWeb.PendingTransactionController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
alias BlockScoutWeb.{Controller, TransactionView}
alias Explorer.Chain
alias Phoenix.View
- {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
+ {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string())
@burn_address_hash burn_address_hash
def index(conn, %{"type" => "JSON"} = params) do
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex
index d692d6de1471..0863579eab95 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex
@@ -1,27 +1,34 @@
defmodule BlockScoutWeb.RecentTransactionsController do
use BlockScoutWeb, :controller
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
+
alias Explorer.{Chain, PagingOptions}
- alias Explorer.Chain.Hash
+ alias Explorer.Chain.{DenormalizationHelper, Hash}
alias Phoenix.View
- {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
+ {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string())
@burn_address_hash burn_address_hash
def index(conn, _params) do
if ajax?(conn) do
recent_transactions =
- Chain.recent_collated_transactions(true,
- necessity_by_association: %{
- :block => :required,
- [created_contract_address: :names] => :optional,
- [from_address: :names] => :optional,
- [to_address: :names] => :optional,
- [created_contract_address: :smart_contract] => :optional,
- [from_address: :smart_contract] => :optional,
- [to_address: :smart_contract] => :optional
- },
- paging_options: %PagingOptions{page_size: 5}
+ Chain.recent_collated_transactions(
+ true,
+ DenormalizationHelper.extend_block_necessity(
+ [
+ necessity_by_association: %{
+ [created_contract_address: :names] => :optional,
+ [from_address: :names] => :optional,
+ [to_address: :names] => :optional,
+ [created_contract_address: :smart_contract] => :optional,
+ [from_address: :smart_contract] => :optional,
+ [to_address: :smart_contract] => :optional
+ },
+ paging_options: %PagingOptions{page_size: 5}
+ ],
+ :required
+ )
)
transactions =
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/robots_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/robots_controller.ex
new file mode 100644
index 000000000000..a482fb1a73c7
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/robots_controller.ex
@@ -0,0 +1,13 @@
+defmodule BlockScoutWeb.RobotsController do
+ use BlockScoutWeb, :controller
+
+ def robots(conn, _params) do
+ conn
+ |> render("robots.txt")
+ end
+
+ def sitemap(conn, _params) do
+ conn
+ |> render("sitemap.xml")
+ end
+end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/search_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/search_controller.ex
index b2f639a6e3a9..937c4b2603a6 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/search_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/search_controller.ex
@@ -4,7 +4,7 @@ defmodule BlockScoutWeb.SearchController do
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
alias BlockScoutWeb.{Controller, SearchView}
- alias Explorer.Chain
+ alias Explorer.Chain.Search
alias Phoenix.View
def search_results(conn, %{"q" => query, "type" => "JSON"} = params) do
@@ -13,7 +13,7 @@ defmodule BlockScoutWeb.SearchController do
search_results_plus_one =
paging_options
- |> Chain.joint_search(offset, query)
+ |> Search.joint_search(offset, query)
{search_results, next_page} = split_list_by_page(search_results_plus_one)
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
index 0ba2f294ad38..acaee3f7bd4d 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
@@ -7,8 +7,7 @@ defmodule BlockScoutWeb.SmartContractController do
alias Explorer.SmartContract.{Reader, Writer}
import Explorer.SmartContract.Solidity.Verifier, only: [parse_boolean: 1]
-
- @burn_address "0x0000000000000000000000000000000000000000"
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
def index(conn, %{"hash" => address_hash_string, "type" => contract_type, "action" => action} = params) do
address_options = [
@@ -30,9 +29,9 @@ defmodule BlockScoutWeb.SmartContractController do
address.smart_contract
|> SmartContract.get_implementation_address_hash()
|> Tuple.to_list()
- |> List.first() || @burn_address
+ |> List.first() || burn_address_hash_string()
else
- @burn_address
+ burn_address_hash_string()
end
functions =
@@ -66,7 +65,7 @@ defmodule BlockScoutWeb.SmartContractController do
implementation_abi =
if contract_type == "proxy" do
implementation_address_hash_string
- |> Chain.get_implementation_abi()
+ |> SmartContract.get_smart_contract_abi()
|> Poison.encode!()
else
[]
@@ -137,7 +136,7 @@ defmodule BlockScoutWeb.SmartContractController do
address: %{hash: address_hash},
custom_abi: true,
contract_abi: contract_abi,
- implementation_address: @burn_address,
+ implementation_address: burn_address_hash_string(),
implementation_abi: [],
contract_type: contract_type,
action: action
@@ -196,7 +195,8 @@ defmodule BlockScoutWeb.SmartContractController do
%{method_id: params["method_id"], args: args},
contract_type,
params["from"],
- address.smart_contract && address.smart_contract.abi
+ address.smart_contract && address.smart_contract.abi,
+ true
)
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/contract_controller.ex
index f792297e3a3c..1e9828a4735d 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/contract_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/contract_controller.ex
@@ -6,13 +6,13 @@ defmodule BlockScoutWeb.Tokens.ContractController do
alias BlockScoutWeb.{AccessHelper, TabHelper}
alias Explorer.Chain
- alias Explorer.Chain.Address
+ alias Explorer.Chain.{Address, SmartContract}
def index(conn, %{"token_id" => address_hash_string} = params) do
options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
- :ok <- Chain.check_verified_smart_contract_exists(address_hash),
+ :ok <- SmartContract.check_verified_smart_contract_exists(address_hash),
{:ok, token} <- Chain.token_from_address_hash(address_hash, options),
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params) do
%{type: type, action: action} =
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex
index 7909faf07674..0eaa0115adc8 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex
@@ -8,8 +8,9 @@ defmodule BlockScoutWeb.Tokens.Instance.TransferController do
alias Phoenix.View
import BlockScoutWeb.Chain, only: [split_list_by_page: 1, paging_options: 1, next_page_params: 3]
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
- {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
+ {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string())
@burn_address_hash burn_address_hash
def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id_str, "type" => "JSON"} = params) do
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex
index 2d09db084535..1b7f2c5bb560 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex
@@ -20,6 +20,7 @@ defmodule BlockScoutWeb.Tokens.InventoryController do
unique_token_instances =
Chain.address_to_unique_tokens(
token.contract_address_hash,
+ token,
unique_tokens_paging_options(params)
)
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/tokens_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/tokens_controller.ex
index 4e4f289fcf8a..17ee2823fcd5 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/tokens_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/tokens_controller.ex
@@ -2,9 +2,8 @@ defmodule BlockScoutWeb.TokensController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
-
alias BlockScoutWeb.{Controller, TokensView}
- alias Explorer.Chain
+ alias Explorer.Chain.Token
alias Phoenix.View
def index(conn, %{"type" => "JSON"} = params) do
@@ -19,7 +18,7 @@ defmodule BlockScoutWeb.TokensController do
params
|> paging_options()
- tokens = Chain.list_top_tokens(filter, paging_params)
+ tokens = Token.list_top(filter, paging_params)
{tokens_page, next_page} = split_list_by_page(tokens)
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/transfer_controller.ex
index a349af147408..b36159b82b66 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/transfer_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/transfer_controller.ex
@@ -12,8 +12,9 @@ defmodule BlockScoutWeb.Tokens.TransferController do
alias Phoenix.View
import BlockScoutWeb.Chain, only: [split_list_by_page: 1, paging_options: 1, next_page_params: 3]
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
- {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
+ {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string())
@burn_address_hash burn_address_hash
def index(conn, %{"token_id" => address_hash_string, "type" => "JSON"} = params) do
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex
index 9158d1ca852f..a31baf1ea923 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex
@@ -14,6 +14,7 @@ defmodule BlockScoutWeb.TransactionController do
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
import BlockScoutWeb.Models.GetTransactionTags, only: [get_transaction_with_addresses_tags: 2]
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
alias BlockScoutWeb.{
AccessHelper,
@@ -25,7 +26,7 @@ defmodule BlockScoutWeb.TransactionController do
alias Explorer.{Chain, Market}
alias Explorer.Chain.Cache.Transaction, as: TransactionCache
- alias Explorer.ExchangeRates.Token
+ alias Explorer.Chain.DenormalizationHelper
alias Phoenix.View
@necessity_by_association %{
@@ -37,12 +38,11 @@ defmodule BlockScoutWeb.TransactionController do
:token_transfers => :optional
}
- {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
+ {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string())
@burn_address_hash burn_address_hash
@default_options [
necessity_by_association: %{
- :block => :required,
[created_contract_address: :names] => :optional,
[from_address: :names] => :optional,
[to_address: :names] => :optional,
@@ -55,6 +55,7 @@ defmodule BlockScoutWeb.TransactionController do
def index(conn, %{"type" => "JSON"} = params) do
options =
@default_options
+ |> DenormalizationHelper.extend_block_necessity(:required)
|> Keyword.merge(paging_options(params))
full_options =
@@ -152,16 +153,13 @@ defmodule BlockScoutWeb.TransactionController do
:ok <- Chain.check_transaction_exists(transaction_hash) do
if Chain.transaction_has_token_transfers?(transaction_hash) do
with {:ok, transaction} <-
- Chain.hash_to_transaction(
- transaction_hash,
- necessity_by_association: @necessity_by_association
- ),
+ Chain.hash_to_transaction(transaction_hash, necessity_by_association: @necessity_by_association),
{:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params),
{:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do
render(
conn,
"show_token_transfers.html",
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
block_height: Chain.block_height(),
current_path: Controller.current_full_path(conn),
current_user: current_user(conn),
@@ -190,16 +188,13 @@ defmodule BlockScoutWeb.TransactionController do
end
else
with {:ok, transaction} <-
- Chain.hash_to_transaction(
- transaction_hash,
- necessity_by_association: @necessity_by_association
- ),
+ Chain.hash_to_transaction(transaction_hash, necessity_by_association: @necessity_by_association),
{:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params),
{:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do
render(
conn,
"show_internal_transactions.html",
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
current_path: Controller.current_full_path(conn),
current_user: current_user(conn),
block_height: Chain.block_height(),
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex
index 9f2b6c8fac98..5ea1e447211a 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex
@@ -8,7 +8,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
alias BlockScoutWeb.{AccessHelper, Controller, InternalTransactionView, TransactionController}
alias Explorer.{Chain, Market}
- alias Explorer.ExchangeRates.Token
+ alias Explorer.Chain.DenormalizationHelper
alias Phoenix.View
def index(conn, %{"transaction_id" => transaction_hash_string, "type" => "JSON"} = params) do
@@ -18,20 +18,19 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
{:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params),
{:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do
full_options =
- Keyword.merge(
- [
- necessity_by_association: %{
- [created_contract_address: :names] => :optional,
- [from_address: :names] => :optional,
- [to_address: :names] => :optional,
- [transaction: :block] => :optional,
- [created_contract_address: :smart_contract] => :optional,
- [from_address: :smart_contract] => :optional,
- [to_address: :smart_contract] => :optional
- }
- ],
- paging_options(params)
- )
+ [
+ necessity_by_association: %{
+ [created_contract_address: :names] => :optional,
+ [from_address: :names] => :optional,
+ [to_address: :names] => :optional,
+ [created_contract_address: :smart_contract] => :optional,
+ [from_address: :smart_contract] => :optional,
+ [to_address: :smart_contract] => :optional,
+ :transaction => :optional
+ }
+ ]
+ |> DenormalizationHelper.extend_transaction_block_necessity(:optional)
+ |> Keyword.merge(paging_options(params))
internal_transactions_plus_one = Chain.transaction_to_internal_transactions(transaction_hash, full_options)
@@ -102,7 +101,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
render(
conn,
"index.html",
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
current_path: Controller.current_full_path(conn),
current_user: current_user(conn),
block_height: Chain.block_height(),
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex
index 70cb686badfc..53ba82ed3571 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex
@@ -8,7 +8,6 @@ defmodule BlockScoutWeb.TransactionLogController do
alias BlockScoutWeb.{AccessHelper, Controller, TransactionController, TransactionLogView}
alias Explorer.{Chain, Market}
- alias Explorer.ExchangeRates.Token
alias Phoenix.View
def index(conn, %{"transaction_id" => transaction_hash_string, "type" => "JSON"} = params) do
@@ -99,7 +98,7 @@ defmodule BlockScoutWeb.TransactionLogController do
current_path: Controller.current_full_path(conn),
current_user: current_user(conn),
transaction: transaction,
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
from_tags: get_address_tags(transaction.from_address_hash, current_user(conn)),
to_tags: get_address_tags(transaction.to_address_hash, current_user(conn)),
tx_tags:
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex
index 0fa47a09f2b9..93002d434822 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex
@@ -8,7 +8,6 @@ defmodule BlockScoutWeb.TransactionRawTraceController do
alias BlockScoutWeb.{AccessHelper, TransactionController}
alias EthereumJSONRPC
alias Explorer.{Chain, Market}
- alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.FirstTraceOnDemand
def index(conn, %{"transaction_id" => hash_string} = params) do
@@ -59,7 +58,7 @@ defmodule BlockScoutWeb.TransactionRawTraceController do
render(
conn,
"index.html",
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
internal_transactions: internal_transactions,
block_height: Chain.block_height(),
current_user: current_user(conn),
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_state_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_state_controller.ex
index 9ebcc0751f13..7ea2d6e74e6c 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_state_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_state_controller.ex
@@ -10,14 +10,15 @@ defmodule BlockScoutWeb.TransactionStateController do
}
alias Explorer.{Chain, Market}
- alias Explorer.ExchangeRates.Token
alias Phoenix.View
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
import BlockScoutWeb.Models.GetTransactionTags, only: [get_transaction_with_addresses_tags: 2]
+ import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
- {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
+ {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string())
@burn_address_hash burn_address_hash
@@ -36,7 +37,18 @@ defmodule BlockScoutWeb.TransactionStateController do
AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params),
{:ok, false} <-
AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do
- state_changes = TransactionStateHelper.state_changes(transaction)
+ state_changes_plus_next_page = transaction |> TransactionStateHelper.state_changes(paging_options(params))
+
+ {state_changes, next_page} = split_list_by_page(state_changes_plus_next_page)
+
+ next_page_url =
+ case next_page_params(next_page, state_changes, params) do
+ nil ->
+ nil
+
+ next_page_params ->
+ transaction_state_path(conn, :index, transaction, Map.delete(next_page_params, "type"))
+ end
rendered_changes =
Enum.map(state_changes, fn state_change ->
@@ -49,13 +61,15 @@ defmodule BlockScoutWeb.TransactionStateController do
balance_before: state_change.balance_before,
balance_after: state_change.balance_after,
balance_diff: state_change.balance_diff,
+ token_id: state_change.token_id,
conn: conn,
miner: state_change.miner?
)
end)
json(conn, %{
- items: rendered_changes
+ items: rendered_changes,
+ next_page_path: next_page_url
})
else
{:restricted_access, _} ->
@@ -93,7 +107,7 @@ defmodule BlockScoutWeb.TransactionStateController do
render(
conn,
"index.html",
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
block_height: Chain.block_height(),
current_path: Controller.current_full_path(conn),
show_token_transfers: Chain.transaction_has_token_transfers?(transaction_hash),
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex
index e2fdffb4ed8d..061fde450ad4 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex
@@ -5,13 +5,13 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
import BlockScoutWeb.Models.GetTransactionTags, only: [get_transaction_with_addresses_tags: 2]
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
alias BlockScoutWeb.{AccessHelper, Controller, TransactionController, TransactionTokenTransferView}
alias Explorer.{Chain, Market}
- alias Explorer.ExchangeRates.Token
alias Phoenix.View
- {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
+ {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string())
@burn_address_hash burn_address_hash
def index(conn, %{"transaction_id" => transaction_hash_string, "type" => "JSON"} = params) do
@@ -105,7 +105,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do
render(
conn,
"index.html",
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
+ exchange_rate: Market.get_coin_exchange_rate(),
block_height: Chain.block_height(),
current_path: Controller.current_full_path(conn),
current_user: current_user(conn),
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/verified_contracts_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/verified_contracts_controller.ex
index 2059498cce2a..1eec03d72678 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/verified_contracts_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/verified_contracts_controller.ex
@@ -8,6 +8,7 @@ defmodule BlockScoutWeb.VerifiedContractsController do
alias BlockScoutWeb.{Controller, VerifiedContractsView}
alias Explorer.Chain
+ alias Explorer.Chain.SmartContract
alias Phoenix.View
@necessity_by_association %{[address: :token] => :optional}
@@ -19,7 +20,7 @@ defmodule BlockScoutWeb.VerifiedContractsController do
|> Keyword.merge(current_filter(params))
|> Keyword.merge(search_query(params))
- verified_contracts_plus_one = Chain.verified_contracts(full_options)
+ verified_contracts_plus_one = SmartContract.verified_contracts(full_options)
{verified_contracts, next_page} = split_list_by_page(verified_contracts_plus_one)
items =
diff --git a/apps/block_scout_web/lib/block_scout_web/endpoint.ex b/apps/block_scout_web/lib/block_scout_web/endpoint.ex
index ed33da3db505..10d8d99f36b6 100644
--- a/apps/block_scout_web/lib/block_scout_web/endpoint.ex
+++ b/apps/block_scout_web/lib/block_scout_web/endpoint.ex
@@ -29,7 +29,6 @@ defmodule BlockScoutWeb.Endpoint do
browserconfig.xml
mstile-150x150.png
safari-pinned-tab.svg
- robots.txt
),
only_matching: ~w(manifest)
)
@@ -67,7 +66,8 @@ defmodule BlockScoutWeb.Endpoint do
signing_salt: "iC2ksJHS",
same_site: "Lax",
http_only: false,
- domain: Application.compile_env(:block_scout_web, :cookie_domain)
+ domain: Application.compile_env(:block_scout_web, :cookie_domain),
+ max_age: Application.compile_env(:block_scout_web, :session_cookie_ttl)
)
use SpandexPhoenix
diff --git a/apps/block_scout_web/lib/block_scout_web/main_page_realtime_event_handler.ex b/apps/block_scout_web/lib/block_scout_web/main_page_realtime_event_handler.ex
new file mode 100644
index 000000000000..8c34538f4751
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/main_page_realtime_event_handler.ex
@@ -0,0 +1,29 @@
+defmodule BlockScoutWeb.MainPageRealtimeEventHandler do
+ @moduledoc """
+ Subscribing process for main page broadcast events from realtime.
+ """
+
+ use GenServer
+
+ alias BlockScoutWeb.Notifier
+ alias Explorer.Chain.Events.Subscriber
+ alias Explorer.Counters.Helper
+
+ def start_link(_) do
+ GenServer.start_link(__MODULE__, [], name: __MODULE__)
+ end
+
+ @impl true
+ def init([]) do
+ Helper.create_cache_table(:last_broadcasted_block)
+ Subscriber.to(:blocks, :realtime)
+ Subscriber.to(:transactions, :realtime)
+ {:ok, []}
+ end
+
+ @impl true
+ def handle_info(event, state) do
+ Notifier.handle_event(event)
+ {:noreply, state}
+ end
+end
diff --git a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex
new file mode 100644
index 000000000000..4b6d7edc7477
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex
@@ -0,0 +1,207 @@
+defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do
+ @moduledoc """
+ Module to interact with Transaction Interpretation Service
+ """
+
+ alias BlockScoutWeb.API.V2.{Helper, TokenView, TransactionView}
+ alias Explorer.Chain
+ alias Explorer.Chain.Transaction
+ alias HTTPoison.Response
+
+ import Explorer.Utility.Microservice, only: [base_url: 2]
+
+ require Logger
+
+ @post_timeout :timer.minutes(5)
+ @request_error_msg "Error while sending request to Transaction Interpretation Service"
+ @api_true api?: true
+ @items_limit 50
+
+ @spec interpret(Transaction.t()) :: {:error, :disabled | binary | Jason.DecodeError.t()} | {:ok, any}
+ def interpret(transaction) do
+ if enabled?() do
+ url = interpret_url()
+
+ body = prepare_request_body(transaction)
+
+ http_post_request(url, body)
+ else
+ {:error, :disabled}
+ end
+ end
+
+ def get_request_body(transaction) do
+ prepare_request_body(transaction)
+ end
+
+ defp http_post_request(url, body) do
+ headers = [{"Content-Type", "application/json"}]
+
+ case HTTPoison.post(url, Jason.encode!(body), headers, recv_timeout: @post_timeout) do
+ {:ok, %Response{body: body, status_code: 200}} ->
+ body |> Jason.decode() |> preload_template_variables()
+
+ error ->
+ old_truncate = Application.get_env(:logger, :truncate)
+ Logger.configure(truncate: :infinity)
+
+ Logger.error(fn ->
+ [
+ "Error while sending request to microservice url: #{url}, body: #{inspect(body, limit: :infinity, printable_limit: :infinity)}: ",
+ inspect(error, limit: :infinity, printable_limit: :infinity)
+ ]
+ end)
+
+ Logger.configure(truncate: old_truncate)
+ {:error, @request_error_msg}
+ end
+ end
+
+ defp config do
+ Application.get_env(:block_scout_web, __MODULE__)
+ end
+
+ def enabled?, do: config()[:enabled]
+
+ defp interpret_url do
+ base_url(:block_scout_web, __MODULE__) <> "/transactions/summary"
+ end
+
+ defp prepare_request_body(transaction) do
+ transaction =
+ Chain.select_repo(@api_true).preload(transaction, [
+ :transaction_actions,
+ to_address: [:names, :smart_contract],
+ from_address: [:names, :smart_contract],
+ created_contract_address: [:names, :token, :smart_contract]
+ ])
+
+ skip_sig_provider? = false
+ {decoded_input, _abi_acc, _methods_acc} = Transaction.decoded_input_data(transaction, skip_sig_provider?, @api_true)
+
+ decoded_input_data = decoded_input |> TransactionView.format_decoded_input() |> TransactionView.decoded_input()
+
+ %{
+ data: %{
+ to: Helper.address_with_info(nil, transaction.to_address, transaction.to_address_hash, true),
+ from:
+ Helper.address_with_info(
+ nil,
+ transaction.from_address,
+ transaction.from_address_hash,
+ true
+ ),
+ hash: transaction.hash,
+ type: transaction.type,
+ value: transaction.value,
+ method: TransactionView.method_name(transaction, decoded_input),
+ status: transaction.status,
+ actions: TransactionView.transaction_actions(transaction.transaction_actions),
+ tx_types: TransactionView.tx_types(transaction),
+ raw_input: transaction.input,
+ decoded_input: decoded_input_data,
+ token_transfers: prepare_token_transfers(transaction, decoded_input)
+ },
+ logs_data: %{items: prepare_logs(transaction)}
+ }
+ end
+
+ defp prepare_token_transfers(transaction, decoded_input) do
+ full_options =
+ [
+ necessity_by_association: %{
+ [from_address: :smart_contract] => :optional,
+ [to_address: :smart_contract] => :optional,
+ [from_address: :names] => :optional,
+ [to_address: :names] => :optional
+ }
+ ]
+ |> Keyword.merge(@api_true)
+
+ transaction.hash
+ |> Chain.transaction_to_token_transfers(full_options)
+ |> Chain.flat_1155_batch_token_transfers()
+ |> Enum.take(@items_limit)
+ |> Enum.map(&TransactionView.prepare_token_transfer(&1, nil, decoded_input))
+ end
+
+ defp prepare_logs(transaction) do
+ full_options =
+ [
+ necessity_by_association: %{
+ [address: :names] => :optional,
+ [address: :smart_contract] => :optional,
+ address: :optional
+ }
+ ]
+ |> Keyword.merge(@api_true)
+
+ logs =
+ transaction.hash
+ |> Chain.transaction_to_logs(full_options)
+ |> Enum.take(@items_limit)
+
+ decoded_logs = TransactionView.decode_logs(logs, true)
+
+ logs
+ |> Enum.zip(decoded_logs)
+ |> Enum.map(fn {log, decoded_log} -> TransactionView.prepare_log(log, transaction.hash, decoded_log, true) end)
+ end
+
+ defp preload_template_variables({:ok, %{"success" => true, "data" => %{"summaries" => summaries} = data}}) do
+ summaries_updated =
+ Enum.map(summaries, fn %{"summary_template_variables" => summary_template_variables} = summary ->
+ summary_template_variables_preloaded =
+ Enum.reduce(summary_template_variables, %{}, fn {key, value}, acc ->
+ Map.put(acc, key, preload_template_variable(value))
+ end)
+
+ Map.put(summary, "summary_template_variables", summary_template_variables_preloaded)
+ end)
+
+ {:ok, %{"success" => true, "data" => Map.put(data, "summaries", summaries_updated)}}
+ end
+
+ defp preload_template_variables(error), do: error
+
+ defp preload_template_variable(%{"type" => "token", "value" => %{"address" => address_hash_string} = value}),
+ do: %{
+ "type" => "token",
+ "value" => address_hash_string |> Chain.token_from_address_hash(@api_true) |> token_from_db() |> Map.merge(value)
+ }
+
+ defp preload_template_variable(%{"type" => "address", "value" => %{"hash" => address_hash_string} = value}),
+ do: %{
+ "type" => "address",
+ "value" =>
+ address_hash_string
+ |> Chain.hash_to_address(
+ [
+ necessity_by_association: %{
+ :names => :optional,
+ :smart_contract => :optional
+ },
+ api?: true
+ ],
+ false
+ )
+ |> address_from_db()
+ |> Map.merge(value)
+ }
+
+ defp preload_template_variable(other), do: other
+
+ defp token_from_db({:error, _}), do: %{}
+ defp token_from_db({:ok, token}), do: TokenView.render("token.json", %{token: token})
+
+ defp address_from_db({:error, _}), do: %{}
+
+ defp address_from_db({:ok, address}),
+ do:
+ Helper.address_with_info(
+ nil,
+ address,
+ address.hash,
+ true
+ )
+end
diff --git a/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex b/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex
index f2255c314086..41b5bd1cfe7b 100644
--- a/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex
+++ b/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex
@@ -1,19 +1,53 @@
defmodule BlockScoutWeb.Models.TransactionStateHelper do
@moduledoc """
- Module includes functions needed for BlockScoutWeb.TransactionStateController
+ Transaction state changes related functions
"""
- alias Explorer.{Chain, Chain.Wei, PagingOptions}
- alias Explorer.Chain
- alias Explorer.Chain.Transaction.StateChange
- alias Indexer.Fetcher.{CoinBalance, TokenBalance}
+ import BlockScoutWeb.Chain, only: [default_paging_options: 0]
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
- {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
+ alias Explorer.Chain.Transaction.StateChange
+ alias Explorer.{Chain, PagingOptions}
+ alias Explorer.Chain.{Block, Transaction, Wei}
+ alias Explorer.Chain.Cache.StateChanges
+ alias Indexer.Fetcher.{CoinBalanceOnDemand, TokenBalanceOnDemand}
+ {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string())
@burn_address_hash burn_address_hash
- # credo:disable-for-next-line /Complexity/
- def state_changes(transaction) do
+ def state_changes(transaction, options \\ [])
+
+ def state_changes(%Transaction{block: %Block{}} = transaction, options) do
+ paging_options = Keyword.get(options, :paging_options, default_paging_options())
+ {offset} = paging_options.key || {0}
+
+ offset
+ |> Kernel.==(0)
+ |> if do
+ get_and_cache_state_changes(transaction, options)
+ else
+ case StateChanges.get(transaction.hash) do
+ %StateChanges{state_changes: state_changes} -> state_changes
+ _ -> get_and_cache_state_changes(transaction, options)
+ end
+ end
+ |> Enum.drop(offset)
+ end
+
+ def state_changes(_transaction, _options), do: []
+
+ defp get_and_cache_state_changes(transaction, options) do
+ state_changes = do_state_changes(transaction, options)
+
+ StateChanges.update(%StateChanges{
+ transaction_hash: transaction.hash,
+ state_changes: state_changes
+ })
+
+ state_changes
+ end
+
+ defp do_state_changes(%Transaction{block: %Block{} = block} = transaction, options) do
transaction_hash = transaction.hash
full_options = [
@@ -26,380 +60,120 @@ defmodule BlockScoutWeb.Models.TransactionStateHelper do
to_address: :required
},
# we need to consider all token transfers in block to show whole state change of transaction
- paging_options: %PagingOptions{key: nil, page_size: nil}
+ paging_options: %PagingOptions{key: nil, page_size: nil},
+ api?: Keyword.get(options, :api?, false)
]
token_transfers = Chain.transaction_to_token_transfers(transaction_hash, full_options)
- block = transaction.block
-
block_txs =
Chain.block_to_transactions(block.hash,
necessity_by_association: %{},
- paging_options: %PagingOptions{key: nil, page_size: nil}
+ paging_options: %PagingOptions{key: nil, page_size: nil},
+ api?: Keyword.get(options, :api?, false)
)
- {from_before, to_before, miner_before} = coin_balances_before(transaction, block_txs)
-
- from_hash = transaction.from_address_hash
- to_hash = transaction.to_address_hash
- miner_hash = block.miner_hash
-
- from_coin_entry =
- if from_hash not in [to_hash, miner_hash] do
- from = transaction.from_address
- from_after = do_update_coin_balance_from_tx(from_hash, transaction, from_before, block)
- balance_diff = Wei.sub(from_after, from_before)
-
- if has_diff?(balance_diff) do
- %StateChange{
- coin_or_token_transfers: :coin,
- address: from,
- balance_before: from_before,
- balance_after: from_after,
- balance_diff: balance_diff,
- miner?: false
- }
- end
- end
-
- to_coin_entry =
- if not is_nil(to_hash) and to_hash != miner_hash do
- to = transaction.to_address
- to_after = do_update_coin_balance_from_tx(to_hash, transaction, to_before, block)
- balance_diff = Wei.sub(to_after, to_before)
-
- if has_diff?(balance_diff) do
- %StateChange{
- coin_or_token_transfers: :coin,
- address: to,
- balance_before: to_before,
- balance_after: to_after,
- balance_diff: balance_diff,
- miner?: false
- }
- end
- end
+ from_before_block = coin_balance(transaction.from_address_hash, block.number - 1, options)
+ to_before_block = coin_balance(transaction.to_address_hash, block.number - 1, options)
+ miner_before_block = coin_balance(block.miner_hash, block.number - 1, options)
- miner = block.miner
- miner_after = do_update_coin_balance_from_tx(miner_hash, transaction, miner_before, block)
- miner_diff = Wei.sub(miner_after, miner_before)
-
- miner_entry =
- if has_diff?(miner_diff) do
- %StateChange{
- coin_or_token_transfers: :coin,
- address: miner,
- balance_before: miner_before,
- balance_after: miner_after,
- balance_diff: miner_diff,
- miner?: true
- }
- end
+ {from_before_tx, to_before_tx, miner_before_tx} =
+ StateChange.coin_balances_before(transaction, block_txs, from_before_block, to_before_block, miner_before_block)
- token_balances_before = token_balances_before(token_transfers, transaction, block_txs)
+ native_coin_entries = StateChange.native_coin_entries(transaction, from_before_tx, to_before_tx, miner_before_tx)
- token_balances_after =
- do_update_token_balances_from_token_transfers(
- token_transfers,
- token_balances_before,
- :include_transfers
- )
+ token_balances_before =
+ token_transfers
+ |> Enum.reduce(%{}, &token_transfers_to_balances_reducer(&1, &2, options))
+ |> StateChange.token_balances_before(transaction, block_txs)
- items =
- for {address, balances} <- token_balances_after,
- {token_hash, {balance, transfers}} <- balances do
- balance_before = token_balances_before[address][token_hash]
- balance_diff = Decimal.sub(balance, balance_before)
- transfer = elem(List.first(transfers), 1)
-
- if transfer.token.type != "ERC-20" or has_diff?(balance_diff) do
- %StateChange{
- coin_or_token_transfers: transfers,
- address: address,
- balance_before: balance_before,
- balance_after: balance,
- balance_diff: balance_diff,
- miner?: false
- }
- end
- end
+ tokens_entries = StateChange.token_entries(token_transfers, token_balances_before)
- [from_coin_entry, to_coin_entry, miner_entry | items]
- |> Enum.reject(&is_nil/1)
- |> Enum.sort_by(fn state_change -> to_string(state_change.address && state_change.address.hash) end)
+ native_coin_entries ++ tokens_entries
end
- defp coin_balance(address_hash, block_number, retry? \\ false)
-
- defp coin_balance(address_hash, _block_number, _retry?) when is_nil(address_hash) do
+ defp coin_balance(address_hash, _block_number, _options) when is_nil(address_hash) do
%Wei{value: Decimal.new(0)}
end
- defp coin_balance(address_hash, block_number, retry?) do
- case Chain.get_coin_balance(address_hash, block_number) do
+ defp coin_balance(address_hash, block_number, options) do
+ case Chain.get_coin_balance(address_hash, block_number, options) do
%{value: val} when not is_nil(val) ->
val
_ ->
- if retry? do
- %Wei{value: Decimal.new(0)}
- else
- json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
- CoinBalance.run([{address_hash.bytes, block_number}], json_rpc_named_arguments)
- # after CoinBalance.run balance is fetched and imported, so we can call coin_balance again
- coin_balance(address_hash, block_number, true)
- end
+ CoinBalanceOnDemand.trigger_historic_fetch(address_hash, block_number)
+ %Wei{value: Decimal.new(0)}
end
end
- defp coin_balances_before(tx, block_txs) do
- block = tx.block
-
- from_before = coin_balance(tx.from_address_hash, block.number - 1)
- to_before = coin_balance(tx.to_address_hash, block.number - 1)
- miner_before = coin_balance(block.miner_hash, block.number - 1)
-
- block_txs
- |> Enum.reduce_while(
- {from_before, to_before, miner_before},
- fn block_tx, {block_from, block_to, block_miner} = state ->
- if block_tx.index < tx.index do
- {:cont,
- {do_update_coin_balance_from_tx(tx.from_address_hash, block_tx, block_from, block),
- do_update_coin_balance_from_tx(tx.to_address_hash, block_tx, block_to, block),
- do_update_coin_balance_from_tx(tx.block.miner_hash, block_tx, block_miner, block)}}
- else
- # txs ordered by index ascending, so we can halt after facing index greater or equal than index of our tx
- {:halt, state}
- end
- end
- )
- end
+ defp token_balances(address_hash, token_transfer, block_number, options) do
+ token = token_transfer.token
- defp do_update_coin_balance_from_tx(address_hash, tx, balance, block) do
- from = tx.from_address_hash
- to = tx.to_address_hash
- miner = block.miner_hash
+ token_ids =
+ if token.type == "ERC-1155" do
+ token_transfer.token_ids
+ else
+ [nil]
+ end
- balance
- |> (&if(address_hash == from, do: Wei.sub(&1, from_loss(tx)), else: &1)).()
- |> (&if(address_hash == to, do: Wei.sum(&1, to_profit(tx)), else: &1)).()
- |> (&if(address_hash == miner, do: Wei.sum(&1, miner_profit(tx, block)), else: &1)).()
+ Enum.into(token_ids, %{transfers: []}, &{&1, token_balance(address_hash, block_number, token, &1, options)})
end
- defp token_balance(address_hash, token_transfer, block_number, retry? \\ false)
-
- defp token_balance(@burn_address_hash, _token_transfer, _block_number, _retry?) do
+ defp token_balance(@burn_address_hash, _block_number, _token, _token_id, _options) do
Decimal.new(0)
end
- defp token_balance(address_hash, token_transfer, block_number, retry?) do
- token = token_transfer.token
- token_contract_address_hash = token.contract_address_hash
-
- case Chain.get_token_balance(address_hash, token_contract_address_hash, block_number) do
+ defp token_balance(address_hash, block_number, token, token_id, options) do
+ case Chain.get_token_balance(address_hash, token.contract_address_hash, block_number, token_id, options) do
%{value: val} when not is_nil(val) ->
val
- # we haven't fetched this balance yet
_ ->
- if retry? do
- Decimal.new(0)
- else
- json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
-
- token_id_int = parse_token_id(token_transfer)
-
- TokenBalance.run(
- [
- {address_hash.bytes, token_contract_address_hash.bytes, block_number, token.type, token_id_int, 0}
- ],
- json_rpc_named_arguments
- )
-
- # after TokenBalance.run balance is fetched and imported, so we can call token_balance again
- token_balance(address_hash, token_transfer, block_number, true)
- end
- end
- end
+ TokenBalanceOnDemand.trigger_historic_fetch(
+ address_hash,
+ token.contract_address_hash,
+ token.type,
+ token_id,
+ block_number
+ )
- defp parse_token_id(token_transfer) do
- case token_transfer.token_id do
- %Decimal{} -> Decimal.to_integer(token_transfer.token_id)
- id_int when is_integer(id_int) -> id_int
- _ -> token_transfer.token_id
+ Decimal.new(0)
end
end
- defp token_balances_before(token_transfers, tx, block_txs) do
- balances_before =
- token_transfers
- |> Enum.reduce(%{}, fn transfer, balances_map ->
- from = transfer.from_address
- to = transfer.to_address
- token_hash = transfer.token_contract_address_hash
- prev_block = transfer.block_number - 1
-
- balances_with_from =
- case balances_map do
- # from address already in the map
- %{^from => %{^token_hash => _}} ->
- balances_map
-
- # we need to add from address into the map
- _ ->
- put_in(
- balances_map,
- Enum.map([from, token_hash], &Access.key(&1, %{})),
- token_balance(from.hash, transfer, prev_block)
- )
- end
-
- case balances_with_from do
- # to address already in the map
- %{^to => %{^token_hash => _}} ->
- balances_with_from
-
- # we need to add to address into the map
- _ ->
- put_in(
- balances_with_from,
- Enum.map([to, token_hash], &Access.key(&1, %{})),
- token_balance(to.hash, transfer, prev_block)
- )
- end
- end)
-
- block_txs
- |> Enum.reduce_while(
- balances_before,
- fn block_tx, state ->
- if block_tx.index < tx.index do
- {:cont, do_update_token_balances_from_token_transfers(block_tx.token_transfers, state)}
- else
- # txs ordered by index ascending, so we can halt after facing index greater or equal than index of our tx
- {:halt, state}
- end
- end
- )
- end
-
- defp do_update_token_balances_from_token_transfers(
- token_transfers,
- balances_map,
- include_transfers \\ :no
- ) do
- Enum.reduce(
- token_transfers,
- balances_map,
- &token_transfers_balances_reducer(&1, &2, include_transfers)
- )
- end
-
- defp token_transfers_balances_reducer(transfer, state_balances_map, include_transfers) do
+ defp token_transfers_to_balances_reducer(transfer, balances, options) do
from = transfer.from_address
to = transfer.to_address
- token = transfer.token_contract_address_hash
-
- balances_map_from_included =
- case state_balances_map do
- # from address is needed to be updated in our map
- %{^from => %{^token => val}} ->
- put_in(
- state_balances_map,
- Enum.map([from, token], &Access.key(&1, %{})),
- do_update_balance(val, :from, transfer, include_transfers)
- )
-
- # we are not interested in this address
- _ ->
- state_balances_map
- end
+ token_hash = transfer.token_contract_address_hash
+ prev_block = transfer.block_number - 1
+
+ balances
+ |> case do
+ # from address already in the map
+ %{^from => %{^token_hash => _}} = balances ->
+ balances
- case balances_map_from_included do
- # to address is needed to be updated in our map
- %{^to => %{^token => val}} ->
+ # we need to add from address into the map
+ balances ->
put_in(
- balances_map_from_included,
- Enum.map([to, token], &Access.key(&1, %{})),
- do_update_balance(val, :to, transfer, include_transfers)
+ balances,
+ Enum.map([from, token_hash], &Access.key(&1, %{})),
+ token_balances(from.hash, transfer, prev_block, options)
)
-
- # we are not interested in this address
- _ ->
- balances_map_from_included
- end
- end
-
- # point of this function is to include all transfers for frontend if option :include_transfer is passed
- defp do_update_balance(old_val, type, transfer, include_transfers) do
- transfer_amount = if is_nil(transfer.amount), do: 1, else: transfer.amount
-
- case {include_transfers, old_val, type} do
- {:include_transfers, {val, transfers}, :from} ->
- {Decimal.sub(val, transfer_amount), [{type, transfer} | transfers]}
-
- {:include_transfers, {val, transfers}, :to} ->
- {Decimal.add(val, transfer_amount), [{type, transfer} | transfers]}
-
- {:include_transfers, val, :from} ->
- {Decimal.sub(val, transfer_amount), [{type, transfer}]}
-
- {:include_transfers, val, :to} ->
- {Decimal.add(val, transfer_amount), [{type, transfer}]}
-
- {_, val, :from} ->
- Decimal.sub(val, transfer_amount)
-
- {_, val, :to} ->
- Decimal.add(val, transfer_amount)
- end
- end
-
- def from_loss(tx) do
- {_, fee} = Chain.fee(tx, :wei)
-
- if error?(tx) do
- %Wei{value: fee}
- else
- Wei.sum(tx.value, %Wei{value: fee})
end
- end
-
- def to_profit(tx) do
- if error?(tx) do
- %Wei{value: 0}
- else
- tx.value
- end
- end
-
- defp miner_profit(tx, block) do
- base_fee_per_gas = block.base_fee_per_gas || %Wei{value: Decimal.new(0)}
- max_priority_fee_per_gas = tx.max_priority_fee_per_gas || tx.gas_price
- max_fee_per_gas = tx.max_fee_per_gas || tx.gas_price
+ |> case do
+ # to address already in the map
+ %{^to => %{^token_hash => _}} = balances ->
+ balances
- priority_fee_per_gas =
- Enum.min_by([max_priority_fee_per_gas, Wei.sub(max_fee_per_gas, base_fee_per_gas)], fn x ->
- Wei.to(x, :wei)
- end)
-
- Wei.mult(priority_fee_per_gas, tx.gas_used)
- end
-
- defp error?(tx) do
- case Chain.transaction_to_status(tx) do
- {:error, _} -> true
- _ -> false
+ # we need to add to address into the map
+ balances ->
+ put_in(
+ balances,
+ Enum.map([to, token_hash], &Access.key(&1, %{})),
+ token_balances(to.hash, transfer, prev_block, options)
+ )
end
end
-
- def has_diff?(%Wei{value: val}) do
- not Decimal.eq?(val, Decimal.new(0))
- end
-
- def has_diff?(val) do
- not Decimal.eq?(val, Decimal.new(0))
- end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/notifier.ex b/apps/block_scout_web/lib/block_scout_web/notifier.ex
index e70ff4202910..3a70cd316594 100644
--- a/apps/block_scout_web/lib/block_scout_web/notifier.ex
+++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex
@@ -3,6 +3,8 @@ defmodule BlockScoutWeb.Notifier do
Responds to events by sending appropriate channel updates to front-end.
"""
+ require Logger
+
alias Absinthe.Subscription
alias BlockScoutWeb.API.V2, as: API_V2
@@ -17,18 +19,18 @@ defmodule BlockScoutWeb.Notifier do
}
alias Explorer.{Chain, Market, Repo}
- alias Explorer.Chain.{Address, InternalTransaction, Transaction}
+ alias Explorer.Chain.Address.Counters
+ alias Explorer.Chain.{Address, DenormalizationHelper, InternalTransaction, Transaction}
alias Explorer.Chain.Supply.RSK
alias Explorer.Chain.Transaction.History.TransactionStats
alias Explorer.Counters.{AverageBlockTime, Helper}
- alias Explorer.ExchangeRates.Token
alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler}
alias Phoenix.View
@check_broadcast_sequence_period 500
def handle_event({:chain_event, :addresses, type, addresses}) when type in [:realtime, :on_demand] do
- Endpoint.broadcast("addresses:new_address", "count", %{count: Chain.address_estimated_count()})
+ Endpoint.broadcast("addresses:new_address", "count", %{count: Counters.address_estimated_count()})
addresses
|> Stream.reject(fn %Address{fetched_coin_balance: fetched_coin_balance} -> is_nil(fetched_coin_balance) end)
@@ -45,14 +47,11 @@ defmodule BlockScoutWeb.Notifier do
Enum.each(address_token_balances, &broadcast_address_token_balance/1)
end
- def handle_event({:chain_event, :address_current_token_balances, type, address_current_token_balances})
- when type in [:realtime, :on_demand] do
- Enum.each(address_current_token_balances, &broadcast_address_token_balance/1)
- end
-
def handle_event(
{:chain_event, :contract_verification_result, :on_demand, {address_hash, contract_verification_result}}
) do
+ log_broadcast_verification_results_for_address(address_hash)
+
Endpoint.broadcast(
"addresses:#{address_hash}",
"verification_result",
@@ -65,6 +64,7 @@ defmodule BlockScoutWeb.Notifier do
def handle_event(
{:chain_event, :contract_verification_result, :on_demand, {address_hash, contract_verification_result, conn}}
) do
+ log_broadcast_verification_results_for_address(address_hash)
%{view: view, compiler: compiler} = select_contract_type_and_form_view(conn.params)
contract_verification_result =
@@ -80,7 +80,7 @@ defmodule BlockScoutWeb.Notifier do
|> View.render_to_string("new.html",
changeset: changeset,
compiler_versions: compiler_versions,
- evm_versions: CodeCompiler.allowed_evm_versions(),
+ evm_versions: CodeCompiler.evm_versions(:solidity),
address_hash: address_hash,
conn: conn,
retrying: true
@@ -114,8 +114,18 @@ defmodule BlockScoutWeb.Notifier do
end)
end
+ def handle_event({:chain_event, :zkevm_confirmed_batches, :realtime, batches}) do
+ batches
+ |> Enum.sort_by(& &1.number, :asc)
+ |> Enum.each(fn confirmed_batch ->
+ Endpoint.broadcast("zkevm_batches:new_zkevm_confirmed_batch", "new_zkevm_confirmed_batch", %{
+ batch: confirmed_batch
+ })
+ end)
+ end
+
def handle_event({:chain_event, :exchange_rate}) do
- exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null()
+ exchange_rate = Market.get_coin_exchange_rate()
market_history_data =
case Market.fetch_recent_history() do
@@ -151,7 +161,7 @@ defmodule BlockScoutWeb.Notifier do
|> Stream.map(
&(InternalTransaction.where_nonpending_block()
|> Repo.get_by(transaction_hash: &1.transaction_hash, index: &1.index)
- |> Repo.preload([:from_address, :to_address, transaction: :block]))
+ |> Repo.preload([:from_address, :to_address, :block]))
)
|> Enum.each(&broadcast_internal_transaction/1)
end
@@ -161,7 +171,9 @@ defmodule BlockScoutWeb.Notifier do
all_token_transfers
|> Enum.map(
&(&1
- |> Repo.preload([:from_address, :to_address, :token, transaction: :block]))
+ |> Repo.preload(
+ DenormalizationHelper.extend_transaction_preload([:from_address, :to_address, :token, :transaction])
+ ))
)
transfers_by_token = Enum.group_by(all_token_transfers_full, fn tt -> to_string(tt.token_contract_address_hash) end)
@@ -181,13 +193,11 @@ defmodule BlockScoutWeb.Notifier do
end
def handle_event({:chain_event, :transactions, :realtime, transactions}) do
- preloads = [:block, created_contract_address: :names, from_address: :names, to_address: :names]
+ base_preloads = [:block, created_contract_address: :names, from_address: :names, to_address: :names]
+ preloads = if API_V2.enabled?(), do: [:token_transfers | base_preloads], else: base_preloads
transactions
- |> Enum.map(
- &(&1
- |> Repo.preload(if API_V2.enabled?(), do: [:token_transfers | preloads], else: preloads))
- )
+ |> Repo.preload(preloads)
|> broadcast_transactions_websocket_v2()
|> Enum.map(fn tx ->
# Disable parsing of token transfers from websocket for transaction tab because we display token transfers at a separate tab
@@ -222,11 +232,28 @@ defmodule BlockScoutWeb.Notifier do
Endpoint.broadcast("addresses:#{to_string(address_hash)}", "changed_bytecode", %{})
end
- def handle_event({:chain_event, :smart_contract_was_verified, :on_demand, [address_hash]}) do
- Endpoint.broadcast("addresses:#{to_string(address_hash)}", "smart_contract_was_verified", %{})
+ def handle_event({:chain_event, :smart_contract_was_verified = event, :on_demand, [address_hash]}) do
+ broadcast_automatic_verification_events(event, address_hash)
end
- def handle_event(_), do: nil
+ def handle_event({:chain_event, :smart_contract_was_not_verified = event, :on_demand, [address_hash]}) do
+ broadcast_automatic_verification_events(event, address_hash)
+ end
+
+ def handle_event({:chain_event, :eth_bytecode_db_lookup_started = event, :on_demand, [address_hash]}) do
+ broadcast_automatic_verification_events(event, address_hash)
+ end
+
+ def handle_event({:chain_event, :address_current_token_balances, :on_demand, address_current_token_balances}) do
+ Endpoint.broadcast("addresses:#{address_current_token_balances.address_hash}", "address_current_token_balances", %{
+ address_current_token_balances: address_current_token_balances.address_current_token_balances
+ })
+ end
+
+ def handle_event(event) do
+ Logger.warning("Unknown broadcasted event #{inspect(event)}.")
+ nil
+ end
def fetch_compiler_version(compiler) do
case CompilerVersion.fetch_versions(compiler) do
@@ -323,7 +350,7 @@ defmodule BlockScoutWeb.Notifier do
"balance_update",
%{
address: address,
- exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null()
+ exchange_rate: Market.get_coin_exchange_rate()
}
)
end
@@ -480,4 +507,17 @@ defmodule BlockScoutWeb.Notifier do
Endpoint.broadcast("addresses:#{address_hash}", event, %{map_key => elements})
end
end
+
+ defp log_broadcast_verification_results_for_address(address_hash) do
+ Logger.info("Broadcast smart-contract #{address_hash} verification results")
+ end
+
+ defp log_broadcast_smart_contract_event(address_hash, event) do
+ Logger.info("Broadcast smart-contract #{address_hash}: #{event}")
+ end
+
+ defp broadcast_automatic_verification_events(event, address_hash) do
+ log_broadcast_smart_contract_event(address_hash, event)
+ Endpoint.broadcast("addresses:#{to_string(address_hash)}", to_string(event), %{})
+ end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/paging_helper.ex b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex
index a4437206a83b..406f47b6c761 100644
--- a/apps/block_scout_web/lib/block_scout_web/paging_helper.ex
+++ b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex
@@ -3,13 +3,15 @@ defmodule BlockScoutWeb.PagingHelper do
Helper for fetching filters and other url query parameters
"""
import Explorer.Chain, only: [string_to_transaction_hash: 1]
- alias Explorer.PagingOptions
+ alias Explorer.Chain.Transaction
+ alias Explorer.{PagingOptions, SortingHelper}
@page_size 50
@default_paging_options %PagingOptions{page_size: @page_size + 1}
@allowed_filter_labels ["validated", "pending"]
@allowed_type_labels ["coin_transfer", "contract_call", "contract_creation", "token_transfer", "token_creation"]
@allowed_token_transfer_type_labels ["ERC-20", "ERC-721", "ERC-1155"]
+ @allowed_nft_token_type_labels ["ERC-721", "ERC-1155"]
def paging_options(%{"block_number" => block_number_string, "index" => index_string}, [:validated | _]) do
with {block_number, ""} <- Integer.parse(block_number_string),
@@ -33,14 +35,29 @@ defmodule BlockScoutWeb.PagingHelper do
def paging_options(_params, _filter), do: [paging_options: @default_paging_options]
+ @spec token_transfers_types_options(map()) :: [{:token_type, list}]
def token_transfers_types_options(%{"type" => filters}) do
[
- token_type: filters |> String.upcase() |> parse_filter(@allowed_token_transfer_type_labels)
+ token_type: filters_to_list(filters, @allowed_token_transfer_type_labels)
]
end
def token_transfers_types_options(_), do: [token_type: []]
+ @doc """
+ Parse 'type' query parameter from request option map
+ """
+ @spec nft_token_types_options(map()) :: [{:token_type, list}]
+ def nft_token_types_options(%{"type" => filters}) do
+ [
+ token_type: filters_to_list(filters, @allowed_nft_token_type_labels)
+ ]
+ end
+
+ def nft_token_types_options(_), do: [token_type: []]
+
+ defp filters_to_list(filters, allowed), do: filters |> String.upcase() |> parse_filter(allowed)
+
# sobelow_skip ["DOS.StringToAtom"]
def filter_options(%{"filter" => filter}, fallback) do
filter = filter |> parse_filter(@allowed_filter_labels) |> Enum.map(&String.to_atom/1)
@@ -127,14 +144,17 @@ defmodule BlockScoutWeb.PagingHelper do
def delete_parameters_from_next_page_params(params) when is_map(params) do
params
- |> Map.delete("block_hash_or_number")
- |> Map.delete("transaction_hash")
- |> Map.delete("address_hash")
- |> Map.delete("type")
- |> Map.delete("method")
- |> Map.delete("filter")
- |> Map.delete("token_address_hash")
- |> Map.delete("q")
+ |> Map.drop([
+ "block_hash_or_number",
+ "transaction_hash_param",
+ "address_hash_param",
+ "type",
+ "method",
+ "filter",
+ "q",
+ "sort",
+ "order"
+ ])
end
def delete_parameters_from_next_page_params(_), do: nil
@@ -147,6 +167,10 @@ defmodule BlockScoutWeb.PagingHelper do
[filter: :vyper]
end
+ def current_filter(%{"filter" => "yul"}) do
+ [filter: :yul]
+ end
+
def current_filter(_), do: []
def search_query(%{"search" => ""}), do: []
@@ -162,4 +186,50 @@ defmodule BlockScoutWeb.PagingHelper do
end
def search_query(_), do: []
+
+ @spec tokens_sorting(%{required(String.t()) => String.t()}) :: [{:sorting, SortingHelper.sorting_params()}]
+ def tokens_sorting(%{"sort" => sort_field, "order" => order}) do
+ [sorting: do_tokens_sorting(sort_field, order)]
+ end
+
+ def tokens_sorting(_), do: []
+
+ defp do_tokens_sorting("fiat_value", "asc"), do: [asc_nulls_first: :fiat_value]
+ defp do_tokens_sorting("fiat_value", "desc"), do: [desc_nulls_last: :fiat_value]
+ defp do_tokens_sorting("holder_count", "asc"), do: [asc_nulls_first: :holder_count]
+ defp do_tokens_sorting("holder_count", "desc"), do: [desc_nulls_last: :holder_count]
+ defp do_tokens_sorting("circulating_market_cap", "asc"), do: [asc_nulls_first: :circulating_market_cap]
+ defp do_tokens_sorting("circulating_market_cap", "desc"), do: [desc_nulls_last: :circulating_market_cap]
+ defp do_tokens_sorting(_, _), do: []
+
+ @spec smart_contracts_sorting(%{required(String.t()) => String.t()}) :: [{:sorting, SortingHelper.sorting_params()}]
+ def smart_contracts_sorting(%{"sort" => sort_field, "order" => order}) do
+ [sorting: do_smart_contracts_sorting(sort_field, order)]
+ end
+
+ def smart_contracts_sorting(_), do: []
+
+ defp do_smart_contracts_sorting("balance", "asc"), do: [{:asc_nulls_first, :fetched_coin_balance, :address}]
+ defp do_smart_contracts_sorting("balance", "desc"), do: [{:desc_nulls_last, :fetched_coin_balance, :address}]
+ defp do_smart_contracts_sorting("txs_count", "asc"), do: [{:asc_nulls_first, :transactions_count, :address}]
+ defp do_smart_contracts_sorting("txs_count", "desc"), do: [{:desc_nulls_last, :transactions_count, :address}]
+ defp do_smart_contracts_sorting(_, _), do: []
+
+ @spec address_transactions_sorting(%{required(String.t()) => String.t()}) :: [
+ {:sorting, SortingHelper.sorting_params()}
+ ]
+ def address_transactions_sorting(%{"sort" => sort_field, "order" => order}) do
+ [sorting: do_address_transaction_sorting(sort_field, order)]
+ end
+
+ def address_transactions_sorting(_), do: []
+
+ defp do_address_transaction_sorting("value", "asc"), do: [asc: :value]
+ defp do_address_transaction_sorting("value", "desc"), do: [desc: :value]
+ defp do_address_transaction_sorting("fee", "asc"), do: [{:dynamic, :fee, :asc_nulls_first, Transaction.dynamic_fee()}]
+
+ defp do_address_transaction_sorting("fee", "desc"),
+ do: [{:dynamic, :fee, :desc_nulls_last, Transaction.dynamic_fee()}]
+
+ defp do_address_transaction_sorting(_, _), do: []
end
diff --git a/apps/block_scout_web/lib/block_scout_web/plug/logger.ex b/apps/block_scout_web/lib/block_scout_web/plug/logger.ex
index 883dd95ef3f8..b61c9cafe4c6 100644
--- a/apps/block_scout_web/lib/block_scout_web/plug/logger.ex
+++ b/apps/block_scout_web/lib/block_scout_web/plug/logger.ex
@@ -19,53 +19,40 @@ defmodule BlockScoutWeb.Plug.Logger do
@impl true
def call(conn, opts) do
level = Keyword.get(opts, :log, :info)
- application = Keyword.get(opts, :application, :block_scout_web)
-
- log(application, conn, level, opts)
start = System.monotonic_time()
Conn.register_before_send(conn, fn conn ->
+ stop = System.monotonic_time()
+ diff = System.convert_time_unit(stop - start, :native, :microsecond)
+ status = Integer.to_string(conn.status)
+
Logger.log(
level,
fn ->
- stop = System.monotonic_time()
- diff = System.convert_time_unit(stop - start, :native, :microsecond)
- status = Integer.to_string(conn.status)
-
- [connection_type(conn), ?\s, status, " in ", formatted_diff(diff)]
+ [connection_type(conn), ?\s, status, " in ", formatted_diff(diff), " on ", conn.method, ?\s, endpoint(conn)]
end,
- opts
+ Keyword.merge(
+ [duration: diff, status: status, unit: "microsecond", endpoint: endpoint(conn), method: conn.method],
+ opts
+ )
)
conn
end)
end
- defp log(:api, conn, level, opts) do
- endpoint =
- if conn.query_string do
- "#{conn.request_path}?#{conn.query_string}"
- else
- conn.request_path
- end
-
- Logger.log(level, endpoint, opts)
- end
-
- defp log(_application, conn, level, opts) do
- Logger.log(
- level,
- fn ->
- [conn.method, ?\s, conn.request_path]
- end,
- opts
- )
- end
-
defp formatted_diff(diff) when diff > 1000, do: [diff |> div(1000) |> Integer.to_string(), "ms"]
defp formatted_diff(diff), do: [Integer.to_string(diff), "µs"]
defp connection_type(%{state: :set_chunked}), do: "Chunked"
defp connection_type(_), do: "Sent"
+
+ defp endpoint(conn) do
+ if conn.query_string do
+ "#{conn.request_path}?#{conn.query_string}"
+ else
+ conn.request_path
+ end
+ end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/plug/redis_cookie.ex b/apps/block_scout_web/lib/block_scout_web/plug/redis_cookie.ex
index 092e5548858c..9b42d551d7d1 100644
--- a/apps/block_scout_web/lib/block_scout_web/plug/redis_cookie.ex
+++ b/apps/block_scout_web/lib/block_scout_web/plug/redis_cookie.ex
@@ -7,6 +7,8 @@ defmodule BlockScoutWeb.Plug.RedisCookie do
require Logger
@behaviour Plug.Session.Store
+ import Explorer.ThirdPartyIntegrations.Auth0, only: [cookie_key: 1]
+
alias Plug.Crypto
alias Plug.Crypto.{KeyGenerator, MessageEncryptor, MessageVerifier}
@@ -192,21 +194,28 @@ defmodule BlockScoutWeb.Plug.RedisCookie do
defp build_rotating_opts(opts, _), do: Map.put(opts, :rotating_options, [])
defp store_to_redis(cookie) do
- Redix.command(:redix, ["SET", hash(cookie), 1])
+ Redix.command(:redix, [
+ "SET",
+ cookie_key(hash(cookie)),
+ 1,
+ "EX",
+ Application.get_env(:block_scout_web, :session_cookie_ttl)
+ ])
cookie
end
defp remove_from_redis(sid) do
- Redix.command(:redix, ["DEL", sid])
+ Redix.command(:redix, ["DEL", cookie_key(sid)])
end
defp check_in_redis({sid, map}, _cookie) when is_nil(sid) or map == %{}, do: {nil, %{}}
defp check_in_redis({_sid, session}, cookie) do
hash = hash(cookie)
+ key = cookie_key(hash)
- case Redix.command(:redix, ["GET", hash]) do
+ case Redix.command(:redix, ["GET", key]) do
{:ok, one} when one in [1, "1"] ->
{hash, session}
diff --git a/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex
index 7c0a05969912..1fedc2dc3dba 100644
--- a/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex
+++ b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex
@@ -7,7 +7,6 @@ defmodule BlockScoutWeb.RealtimeEventHandler do
alias BlockScoutWeb.Notifier
alias Explorer.Chain.Events.Subscriber
- alias Explorer.Counters.Helper
def start_link(_) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
@@ -15,22 +14,20 @@ defmodule BlockScoutWeb.RealtimeEventHandler do
@impl true
def init([]) do
- Helper.create_cache_table(:last_broadcasted_block)
Subscriber.to(:address_coin_balances, :realtime)
Subscriber.to(:addresses, :realtime)
Subscriber.to(:block_rewards, :realtime)
- Subscriber.to(:blocks, :realtime)
Subscriber.to(:internal_transactions, :realtime)
Subscriber.to(:internal_transactions, :on_demand)
Subscriber.to(:token_transfers, :realtime)
- Subscriber.to(:transactions, :realtime)
Subscriber.to(:addresses, :on_demand)
Subscriber.to(:address_coin_balances, :on_demand)
+ Subscriber.to(:address_current_token_balances, :on_demand)
Subscriber.to(:address_token_balances, :on_demand)
- Subscriber.to(:contract_verification_result, :on_demand)
Subscriber.to(:token_total_supply, :on_demand)
Subscriber.to(:changed_bytecode, :on_demand)
- Subscriber.to(:smart_contract_was_verified, :on_demand)
+ Subscriber.to(:eth_bytecode_db_lookup_started, :on_demand)
+ Subscriber.to(:zkevm_confirmed_batches, :realtime)
# Does not come from the indexer
Subscriber.to(:exchange_rate)
Subscriber.to(:transaction_stats)
diff --git a/apps/block_scout_web/lib/block_scout_web/router.ex b/apps/block_scout_web/lib/block_scout_web/router.ex
index 8b7421b2773b..b56899ce80f3 100644
--- a/apps/block_scout_web/lib/block_scout_web/router.ex
+++ b/apps/block_scout_web/lib/block_scout_web/router.ex
@@ -28,12 +28,6 @@ defmodule BlockScoutWeb.Router do
# Needs to be 200 to support the schema introspection for graphiql
@max_complexity 200
- forward("/graphql", Absinthe.Plug,
- schema: BlockScoutWeb.Schema,
- analyze_complexity: true,
- max_complexity: @max_complexity
- )
-
forward("/graphiql", Absinthe.Plug.GraphiQL,
schema: BlockScoutWeb.Schema,
interface: :advanced,
@@ -53,6 +47,8 @@ defmodule BlockScoutWeb.Router do
scope "/", BlockScoutWeb do
pipe_through(:browser)
+ get("/robots.txt", RobotsController, :robots)
+ get("/sitemap.xml", RobotsController, :sitemap)
get("/api-docs", APIDocsController, :index)
get("/eth-rpc-api-docs", APIDocsController, :eth_rpc)
end
diff --git a/apps/block_scout_web/lib/block_scout_web/schema/types.ex b/apps/block_scout_web/lib/block_scout_web/schema/types.ex
index 426d9909f59b..d81f6eca5137 100644
--- a/apps/block_scout_web/lib/block_scout_web/schema/types.ex
+++ b/apps/block_scout_web/lib/block_scout_web/schema/types.ex
@@ -22,10 +22,14 @@ defmodule BlockScoutWeb.Schema.Types do
A stored representation of a Web3 address.
"""
object :address do
- field(:hash, :address_hash)
field(:fetched_coin_balance, :wei)
field(:fetched_coin_balance_block_number, :integer)
+ field(:hash, :address_hash)
field(:contract_code, :data)
+ field(:nonce, :integer)
+ field(:gas_used, :integer)
+ field(:transactions_count, :integer)
+ field(:token_transfers_count, :integer)
field :smart_contract, :smart_contract do
resolve(dataloader(:db, :smart_contract))
@@ -36,16 +40,7 @@ defmodule BlockScoutWeb.Schema.Types do
arg(:order, type: :sort_order, default_value: :desc)
resolve(&Transaction.get_by/3)
- complexity(fn
- %{first: first}, child_complexity ->
- first * child_complexity
-
- %{last: last}, child_complexity ->
- last * child_complexity
-
- %{}, _child_complexity ->
- 0
- end)
+ complexity(fn params, child_complexity -> process_complexity(params, child_complexity) end)
end
end
@@ -55,18 +50,20 @@ defmodule BlockScoutWeb.Schema.Types do
structure that they form is called a "blockchain".
"""
object :block do
- field(:hash, :full_hash)
field(:consensus, :boolean)
field(:difficulty, :decimal)
field(:gas_limit, :decimal)
field(:gas_used, :decimal)
+ field(:hash, :full_hash)
+ field(:miner_hash, :address_hash)
field(:nonce, :nonce_hash)
field(:number, :integer)
+ field(:parent_hash, :full_hash)
field(:size, :integer)
field(:timestamp, :datetime)
field(:total_difficulty, :decimal)
- field(:miner_hash, :address_hash)
- field(:parent_hash, :full_hash)
+ field(:base_fee_per_gas, :wei)
+ field(:is_empty, :boolean)
end
@desc """
@@ -85,12 +82,14 @@ defmodule BlockScoutWeb.Schema.Types do
field(:trace_address, :json)
field(:type, :type)
field(:value, :wei)
- field(:block_number, :integer)
- field(:transaction_index, :integer)
field(:created_contract_address_hash, :address_hash)
field(:from_address_hash, :address_hash)
field(:to_address_hash, :address_hash)
field(:transaction_hash, :full_hash)
+ field(:block_number, :integer)
+ field(:transaction_index, :integer)
+ field(:block_hash, :full_hash)
+ field(:block_index, :integer)
end
@desc """
@@ -108,6 +107,19 @@ defmodule BlockScoutWeb.Schema.Types do
field(:contract_source_code, :string)
field(:abi, :json)
field(:address_hash, :address_hash)
+ field(:constructor_arguments, :string)
+ field(:optimization_runs, :integer)
+ field(:evm_version, :string)
+ field(:external_libraries, :json)
+ field(:verified_via_sourcify, :boolean)
+ field(:partially_verified, :boolean)
+ field(:file_path, :string)
+ field(:is_vyper_contract, :boolean)
+ field(:is_changed_bytecode, :boolean)
+ field(:implementation_name, :string)
+ field(:implementation_address_hash, :address_hash)
+ field(:compiler_settings, :json)
+ field(:verified_via_eth_bytecode_db, :boolean)
end
@desc """
@@ -118,7 +130,6 @@ defmodule BlockScoutWeb.Schema.Types do
field(:amounts, list_of(:decimal))
field(:block_number, :integer)
field(:log_index, :integer)
- field(:token_id, :decimal)
field(:token_ids, list_of(:decimal))
field(:from_address_hash, :address_hash)
field(:to_address_hash, :address_hash)
@@ -130,13 +141,12 @@ defmodule BlockScoutWeb.Schema.Types do
Models a Web3 transaction.
"""
node object(:transaction, id_fetcher: &transaction_id_fetcher/2) do
- field(:hash, :full_hash)
- field(:block_number, :integer)
field(:cumulative_gas_used, :decimal)
field(:error, :string)
field(:gas, :decimal)
field(:gas_price, :wei)
field(:gas_used, :decimal)
+ field(:hash, :full_hash)
field(:index, :integer)
field(:input, :string)
field(:nonce, :nonce_hash)
@@ -145,24 +155,23 @@ defmodule BlockScoutWeb.Schema.Types do
field(:status, :status)
field(:v, :decimal)
field(:value, :wei)
+ field(:block_hash, :full_hash)
+ field(:block_number, :integer)
field(:from_address_hash, :address_hash)
field(:to_address_hash, :address_hash)
field(:created_contract_address_hash, :address_hash)
+ field(:earliest_processing_start, :datetime)
+ field(:revert_reason, :string)
+ field(:max_priority_fee_per_gas, :wei)
+ field(:max_fee_per_gas, :wei)
+ field(:type, :integer)
+ field(:has_error_in_internal_txs, :boolean)
connection field(:internal_transactions, node_type: :internal_transaction) do
arg(:count, :integer)
resolve(&InternalTransaction.get_by/3)
- complexity(fn
- %{first: first}, child_complexity ->
- first * child_complexity
-
- %{last: last}, child_complexity ->
- last * child_complexity
-
- %{}, _child_complexity ->
- 0
- end)
+ complexity(fn params, child_complexity -> process_complexity(params, child_complexity) end)
end
end
@@ -175,4 +184,17 @@ defmodule BlockScoutWeb.Schema.Types do
def internal_transaction_id_fetcher(%{transaction_hash: transaction_hash, index: index}, _) do
Jason.encode!(%{transaction_hash: to_string(transaction_hash), index: index})
end
+
+ defp process_complexity(params, child_complexity) do
+ case params do
+ %{first: first} ->
+ first * child_complexity
+
+ %{last: last} ->
+ last * child_complexity
+
+ %{} ->
+ 0
+ end
+ end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/smart_contract_realtime_event_handler.ex b/apps/block_scout_web/lib/block_scout_web/smart_contract_realtime_event_handler.ex
new file mode 100644
index 000000000000..e94e8a3bb9bb
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/smart_contract_realtime_event_handler.ex
@@ -0,0 +1,28 @@
+defmodule BlockScoutWeb.SmartContractRealtimeEventHandler do
+ @moduledoc """
+ Subscribing process for smart contract verification related broadcast events from realtime.
+ """
+
+ use GenServer
+
+ alias BlockScoutWeb.Notifier
+ alias Explorer.Chain.Events.Subscriber
+
+ def start_link(_) do
+ GenServer.start_link(__MODULE__, [], name: __MODULE__)
+ end
+
+ @impl true
+ def init([]) do
+ Subscriber.to(:contract_verification_result, :on_demand)
+ Subscriber.to(:smart_contract_was_verified, :on_demand)
+ Subscriber.to(:smart_contract_was_not_verified, :on_demand)
+ {:ok, []}
+ end
+
+ @impl true
+ def handle_info(event, state) do
+ Notifier.handle_event(event)
+ {:noreply, state}
+ end
+end
diff --git a/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex b/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex
index a9d698b234d2..ad8bf8ad9609 100644
--- a/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex
+++ b/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex
@@ -27,6 +27,7 @@ defmodule BlockScoutWeb.SmartContractsApiV2Router do
get("/:address_hash/methods-read-proxy", V2.SmartContractController, :methods_read_proxy)
get("/:address_hash/methods-write-proxy", V2.SmartContractController, :methods_write_proxy)
post("/:address_hash/query-read-method", V2.SmartContractController, :query_read_method)
+ get("/:address_hash/solidityscan-report", V2.SmartContractController, :solidityscan_report)
get("/verification/config", V2.VerificationController, :config)
@@ -37,6 +38,7 @@ defmodule BlockScoutWeb.SmartContractsApiV2Router do
post("/multi-part", V2.VerificationController, :verification_via_multi_part)
post("/vyper-code", V2.VerificationController, :verification_via_vyper_code)
post("/vyper-multi-part", V2.VerificationController, :verification_via_vyper_multipart)
+ post("/vyper-standard-input", V2.VerificationController, :verification_via_vyper_standard_input)
end
end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex
index de29c7e7fd9d..a4060c9485d9 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex
@@ -8,7 +8,7 @@
class: "card-tab #{tab_status("transactions", @conn.request_path)}",
to: AccessHelper.get_path(@conn, :address_transaction_path, :index, @address.hash)
) %>
- <%= if Chain.check_if_token_transfers_at_address(@address.hash) do %>
+ <%= if Counters.check_if_token_transfers_at_address(@address.hash) do %>
<%= link(
gettext("Token Transfers"),
class: "card-tab #{tab_status("token-transfers", @conn.request_path)}",
@@ -16,7 +16,7 @@
to: AccessHelper.get_path(@conn, :address_token_transfers_path, :index, @address.hash)
) %>
<% end %>
- <%= if Chain.check_if_tokens_at_address(@address.hash) do %>
+ <%= if Counters.check_if_tokens_at_address(@address.hash) do %>
<%= link(
gettext("Tokens"),
class: "card-tab #{tab_status("tokens", @conn.request_path)}",
@@ -24,7 +24,7 @@
"data-test": "tokens_tab_link"
) %>
<% end %>
- <%= if Chain.check_if_withdrawals_at_address(@address.hash) do %>
+ <%= if Counters.check_if_withdrawals_at_address(@address.hash) do %>
<%= link(
gettext("Withdrawals"),
class: "card-tab #{tab_status("withdrawals", @conn.request_path)}",
@@ -44,14 +44,14 @@
"data-test": "coin_balance_tab_link",
to: AccessHelper.get_path(@conn, :address_coin_balance_path, :index, @address.hash)
) %>
- <%= if Chain.check_if_logs_at_address(@address.hash) do %>
+ <%= if Counters.check_if_logs_at_address(@address.hash) do %>
<%= link(
gettext("Logs"),
class: "card-tab #{tab_status("logs", @conn.request_path)}",
to: AccessHelper.get_path(@conn, :address_logs_path, :index, @address.hash)
) %>
<% end %>
- <%= if Chain.check_if_validated_blocks_at_address(@address.hash) do %>
+ <%= if Counters.check_if_validated_blocks_at_address(@address.hash) do %>
<%= link(
gettext("Blocks Validated"),
class: "card-tab #{tab_status("validations", @conn.request_path)}",
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex
index 368aa9f91e75..04cefd6cde58 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex
@@ -28,7 +28,7 @@
<%= @tx_count %>
- <%= gettext "Transactions sent" %>
+ <%= gettext "Transactions" %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
index 6bd149313fc1..1d6776ecf04d 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
@@ -1,9 +1,9 @@
<% contract_creation_code = contract_creation_code(@address) %>
-<% minimal_proxy_template = Chain.get_minimal_proxy_template(@address.hash) %>
-<% metadata_for_verification = minimal_proxy_template || Chain.get_address_verified_twin_contract(@address.hash).verified_contract %>
+<% minimal_proxy_template = EIP1167.get_implementation_address(@address.hash) %>
+<% metadata_for_verification = minimal_proxy_template || SmartContract.get_address_verified_twin_contract(@address.hash).verified_contract %>
<% smart_contract_verified = BlockScoutWeb.AddressView.smart_contract_verified?(@address) %>
-<% additional_sources_from_twin = Chain.get_address_verified_twin_contract(@address.hash).additional_sources %>
-<% fully_verified = Chain.smart_contract_fully_verified?(@address.hash)%>
+<% additional_sources_from_twin = SmartContract.get_address_verified_twin_contract(@address.hash).additional_sources %>
+<% fully_verified = SmartContract.verified_with_full_match?(@address.hash)%>
<% additional_sources = if smart_contract_verified, do: @address.smart_contract_additional_sources, else: additional_sources_from_twin %>
<% visualize_sol2uml_enabled = Explorer.Visualize.Sol2uml.enabled?() %>
@@ -43,16 +43,16 @@
<% end %>
<%= if smart_contract_verified || (!smart_contract_verified && metadata_for_verification) do %>
<% target_contract = if smart_contract_verified, do: @address.smart_contract, else: metadata_for_verification %>
- <%= if @address.smart_contract.partially_verified && smart_contract_verified do %>
+ <%= if @address.smart_contract && @address.smart_contract.verified_via_sourcify && @address.smart_contract.partially_verified && smart_contract_verified do %>
<%= gettext("This contract has been partially verified via Sourcify.") %>
<% else %>
- <%= if @address.smart_contract.verified_via_sourcify && smart_contract_verified do %>
+ <%= if @address.smart_contract && @address.smart_contract.verified_via_sourcify && smart_contract_verified do %>
<%= gettext("This contract has been verified via Sourcify.") %>
<% end %>
<% end %>
- <%= if @address.smart_contract.verified_via_sourcify && smart_contract_verified do %>
+ <%= if @address.smart_contract && @address.smart_contract.verified_via_sourcify && smart_contract_verified do %>
target="_blank">
View contract in Sourcify repository <%= render BlockScoutWeb.IconsView, "_external_link.html" %>
@@ -66,7 +66,7 @@
<%= gettext "Optimization enabled" %>
- <%= if target_contract.is_vyper_contract, do: "N/A", else: format_optimization_text(target_contract.optimization) %>
+ <%= format_optimization_text(target_contract.optimization) %>
<%= gettext "Compiler version" %>
@@ -97,7 +97,7 @@
<%= gettext "Constructor Arguments" %>
-
<%= raw(format_constructor_arguments(target_contract, @conn)) %>
+ <%= format_constructor_arguments(target_contract, @conn) %>
@@ -249,7 +249,7 @@
<%= gettext "External libraries" %>
-
<%= raw(format_external_libraries(target_contract.external_libraries, @conn)) %>
+ <%= format_external_libraries(target_contract.external_libraries, @conn) %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex
index 383aa4584d4c..b0a22d4bace6 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex
@@ -1,4 +1,4 @@
-<% metadata_for_verification = if assigns[:retrying], do: nil, else: Chain.get_address_verified_twin_contract(@address_hash).verified_contract %>
+<% metadata_for_verification = if assigns[:retrying], do: nil, else: SmartContract.get_address_verified_twin_contract(@address_hash).verified_contract %>
<% changeset = (if assigns[:retrying], do: @changeset, else: SmartContract.merge_twin_contract_with_changeset(metadata_for_verification, @changeset)) |> SmartContract.address_to_checksum_address() %>
<% fetch_constructor_arguments_automatically = if metadata_for_verification, do: true, else: changeset.changes[:autodetect_constructor_args] || true %>
<% display_constructor_arguments_text_area = if fetch_constructor_arguments_automatically, do: "none", else: "block" %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex
index a3b772456f61..938631b3e1aa 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex
@@ -1,4 +1,4 @@
-<% metadata_for_verification = if assigns[:retrying], do: nil, else: Chain.get_address_verified_twin_contract(@address_hash).verified_contract %>
+<% metadata_for_verification = if assigns[:retrying], do: nil, else: SmartContract.get_address_verified_twin_contract(@address_hash).verified_contract %>
<% changeset = (if assigns[:retrying], do: @changeset, else: SmartContract.merge_twin_contract_with_changeset(metadata_for_verification, @changeset)) |> SmartContract.address_to_checksum_address() %>
<%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost") %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex
index 34b8d06cfe9d..a1fb8db78646 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex
@@ -1,4 +1,4 @@
-<% metadata_for_verification = Chain.get_address_verified_twin_contract(@address_hash).verified_contract %>
+<% metadata_for_verification = SmartContract.get_address_verified_twin_contract(@address_hash).verified_contract %>
<% changeset = (if assigns[:retrying], do: @changeset, else: SmartContract.merge_twin_contract_with_changeset(metadata_for_verification, @changeset)) |> SmartContract.address_to_checksum_address() %>
<% fetch_constructor_arguments_automatically = if metadata_for_verification, do: true, else: changeset.changes[:autodetect_constructor_args] || true %>
<% display_constructor_arguments_text_area = if fetch_constructor_arguments_automatically, do: "none", else: "block" %>
@@ -10,7 +10,7 @@
<%= form_for changeset,
address_contract_verification_path(@conn, :create),
[id: "standard-json-dropzone-form"],
- fn f -> %>
+ fn f -> %>
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_contract_address_field.html", f: f %>
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_contract_name_field.html", f: f, tooltip: "Must match the name specified in the code. For example, in contract MyContract {..} MyContract is the contract name. Also contract name could be: path/to/file.sol:MyContract " %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex
index 4b07896c63a6..a98282193b15 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex
@@ -1,4 +1,4 @@
-<% metadata_for_verification = Chain.get_address_verified_twin_contract(@address_hash).verified_contract %>
+<% metadata_for_verification = SmartContract.get_address_verified_twin_contract(@address_hash).verified_contract %>
<% changeset = (if assigns[:retrying], do: @changeset, else: SmartContract.merge_twin_vyper_contract_with_changeset(metadata_for_verification, @changeset)) |> SmartContract.address_to_checksum_address() %>
<%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost") %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex
index 1757deb2cb2b..0a5b2bcf94c9 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex
@@ -60,7 +60,7 @@
- <%= render BlockScoutWeb.CommonComponentsView, "_csv_export_button.html", address: Address.checksum(@address.hash), type: "internal-transactions", conn: @conn %>
+ <%= render BlockScoutWeb.CommonComponentsView, "_csv_export_button.html", address: Address.checksum(@address.hash), type: "internal-transactions", filter_type: :address, filter_value: @filter, conn: @conn %>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex
index eaa5765bc29b..9e213f9457f0 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex
@@ -27,46 +27,43 @@
) %>
- <%= case decoded_result do %>
- <% {:error, :could_not_decode} -> %>
- <%= gettext "Decoded" %>
-
-
- <%= gettext "Failed to decode log data." %>
-
- <% {:ok, method_id, text, mapping} -> %>
- <%= gettext "Decoded" %>
-
-
- <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %>
- <% {:error, :contract_not_verified, results} -> %>
- <%= for {:ok, method_id, text, mapping} <- results do %>
- <%= gettext "Decoded" %>
-
-
- <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %>
-
+ <%= case decoded_result do %>
+ <% {:error, :could_not_decode} -> %>
+ <%= gettext "Decoded" %>
+
+
+ <%= gettext "Failed to decode log data." %>
+
+ <% {:ok, method_id, text, mapping} -> %>
+ <%= gettext "Decoded" %>
+
+
+ <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %>
+ <% {:error, :contract_not_verified, results} -> %>
+ <%= for {:ok, method_id, text, mapping} <- results do %>
+ <%= gettext "Decoded" %>
+
+
+ <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %>
<% end %>
- <% _ -> %>
- <%= nil %>
<% end %>
<%= gettext "Topics" %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token/_tokens.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token/_tokens.html.eex
index 189c6cf0614e..bb5ac9df2210 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_token/_tokens.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token/_tokens.html.eex
@@ -40,7 +40,7 @@
<%= if token_price && @token.decimals do %>
- <%= ChainView.format_usd_value(Chain.balance_in_fiat(@token_balance, @token)) %>
+ <%= ChainView.format_usd_value(Chain.balance_in_fiat(@token_balance)) %>
<% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex
index 65b636fbcc1f..807b11b99609 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex
@@ -47,7 +47,7 @@
placeholder: gettext("Search tokens")
) %>
- <%= if Enum.any?(@token_balances, fn {_token_balance, token} -> token.type == "ERC-721" end) do %>
+ <%= if Enum.any?(@token_balances, fn token_balance -> token_balance.token.type == "ERC-721" end) do %>
<%= render(
"_tokens.html",
conn: @conn,
@@ -56,7 +56,7 @@
) %>
<% end %>
- <%= if Enum.any?(@token_balances, fn {_token_balance, token} -> token.type == "ERC-1155" end) do %>
+ <%= if Enum.any?(@token_balances, fn token_balance -> token_balance.token.type == "ERC-1155" end) do %>
<%= render(
"_tokens.html",
conn: @conn,
@@ -65,7 +65,7 @@
) %>
<% end %>
- <%= if Enum.any?(@token_balances, fn {_token_balance, token} -> token.type == "ERC-20" end) do %>
+ <%= if Enum.any?(@token_balances, fn token_balance -> token_balance.token.type == "ERC-20" end) do %>
<%= render(
"_tokens.html",
conn: @conn,
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_tokens.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_tokens.html.eex
index 65d4d12c3690..1350b792df96 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_tokens.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_tokens.html.eex
@@ -3,17 +3,17 @@
<%= @type %> (<%= Enum.count(@token_balances)%> )
- <%= for {token_balance, token} <- @token_balances do %>
+ <%= for token_balance <- @token_balances do %>
<% path = cond do
- token_balance.token_type == "ERC-721" && !is_nil(token_balance.token_id) -> token_instance_path(@conn, :show, token.contract_address_hash, to_string(token_balance.token_id))
- token_balance.token_type == "ERC-1155" && !is_nil(token_balance.token_id) -> token_instance_path(@conn, :show, token.contract_address_hash, to_string(token_balance.token_id))
- true -> token_path(@conn, :show, to_string(token.contract_address_hash))
+ token_balance.token_type == "ERC-721" && !is_nil(token_balance.token_id) -> token_instance_path(@conn, :show, token_balance.token.contract_address_hash, to_string(token_balance.token_id))
+ token_balance.token_type == "ERC-1155" && !is_nil(token_balance.token_id) -> token_instance_path(@conn, :show, token_balance.token.contract_address_hash, to_string(token_balance.token_id))
+ true -> token_path(@conn, :show, to_string(token_balance.token.contract_address_hash))
end
%>
<%= link(
@@ -23,7 +23,7 @@
<%= if Application.get_env(:block_scout_web, :display_token_icons) do %>
<% chain_id_for_token_icon = Application.get_env(:block_scout_web, :chain_id) %>
- <% address_hash = token.contract_address_hash %>
+ <% address_hash = token_balance.token.contract_address_hash %>
<%=
render BlockScoutWeb.TokensView,
"_token_icon.html",
@@ -32,18 +32,18 @@
additional_classes: ["token-wallet-icon"]
%>
<% end %>
-
"><%= token_name(token) %>
+
"><%= token_name(token_balance.token) %>
- <%= if token.fiat_value && token.decimals do %>
+ <%= if token_balance.token.fiat_value && token_balance.token.decimals do %>
-
+
<% end %>
- <%= if token.fiat_value do %>
+ <%= if token_balance.token.fiat_value do %>
-
+
<% end %>
@@ -52,7 +52,7 @@
<%= if token_balance.token_type == "ERC-721" && !is_nil(token_balance.token_id) do %>
1
<% else %>
- <%= format_according_to_decimals(token_balance.value, token.decimals) %> <%= token_symbol(token) %>
+ <%= format_according_to_decimals(token_balance.value, token_balance.token.decimals) %> <%= token_symbol(token_balance.token) %>
<% end %>
<%= if (token_balance.token_type == "ERC-721" && !is_nil(token_balance.token_id)) or token_balance.token_type == "ERC-1155" do %>
<%= " TokenID " <> to_string(token_balance.token_id) %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex
index 78f0004ee53c..896ea5f8ab61 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex
@@ -64,7 +64,7 @@
- <%= render BlockScoutWeb.CommonComponentsView, "_csv_export_button.html", address: Address.checksum(@address.hash), type: "token-transfers", conn: @conn %>
+ <%= render BlockScoutWeb.CommonComponentsView, "_csv_export_button.html", address: Address.checksum(@address.hash), type: "token-transfers", filter_type: :address, filter_value: @filter, conn: @conn %>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex
index 798b7153a9d1..74f122040a5c 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex
@@ -61,7 +61,7 @@
- <%= render BlockScoutWeb.CommonComponentsView, "_csv_export_button.html", address: Address.checksum(@address.hash), type: "transactions", conn: @conn %>
+ <%= render BlockScoutWeb.CommonComponentsView, "_csv_export_button.html", address: Address.checksum(@address.hash), type: "transactions", filter_type: :address, filter_value: @filter, conn: @conn %>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/_link.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/_link.html.eex
index d910c2ad1941..c498a395bebf 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/block/_link.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/block/_link.html.eex
@@ -1,4 +1,4 @@
<%= link(
gettext("Block #%{number}", number: to_string(@block.number)),
- to: block_path(BlockScoutWeb.Endpoint, :show, @block)
+ to: block_path(BlockScoutWeb.Endpoint, :show, @block.hash)
) %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex
index d7b9c2613400..452c73a3feb0 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex
@@ -1,4 +1,4 @@
-<% burned_fee = if !is_nil(@block.base_fee_per_gas), do: Wei.mult(@block.base_fee_per_gas, BlockBurnedFeeCounter.fetch(@block.hash)), else: nil %>
+<% burnt_fees = if !is_nil(@block.base_fee_per_gas), do: Wei.mult(@block.base_fee_per_gas, BlockBurntFeeCounter.fetch(@block.hash)), else: nil %>
<% priority_fee = if !is_nil(@block.base_fee_per_gas), do: BlockPriorityFeeCounter.fetch(@block.hash), else: nil %>
@@ -61,7 +61,7 @@
<%= format_wei_value(%Wei{value: priority_fee}, :ether) %> <%= gettext "Priority Fees" %>
-
<%= format_wei_value(burned_fee, :ether) %> <%= gettext "Burnt Fees" %>
+
<%= format_wei_value(burnt_fees, :ether) %> <%= gettext "Burnt Fees" %>
<% end %>
<%= formatted_gas(@block.gas_limit) %> <%= gettext "Gas Limit" %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex
index 332ae2b1392a..feee728643a1 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex
@@ -1,4 +1,4 @@
-<% burned_fee = if !is_nil(@block.base_fee_per_gas), do: Wei.mult(@block.base_fee_per_gas, BlockBurnedFeeCounter.fetch(@block.hash)), else: nil %>
+<% burnt_fees = if !is_nil(@block.base_fee_per_gas), do: Wei.mult(@block.base_fee_per_gas, BlockBurntFeeCounter.fetch(@block.hash)), else: nil %>
<% priority_fee = if !is_nil(@block.base_fee_per_gas), do: BlockPriorityFeeCounter.fetch(@block.hash), else: nil %>
<%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %>
@@ -212,10 +212,10 @@
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html",
- text: Explorer.coin_name() <> " " <> gettext("burned from transactions included in the block (Base fee (per unit of gas) * Gas Used).") %>
+ text: Explorer.coin_name() <> " " <> gettext("burnt from transactions included in the block (Base fee (per unit of gas) * Gas Used).") %>
<%= gettext("Burnt Fees") %>
- <%= format_wei_value(burned_fee, :ether) %>
+ <%= format_wei_value(burnt_fees, :ether) %>
@@ -226,7 +226,7 @@
<%= format_wei_value(%Wei{value: priority_fee}, :ether) %>
- <% end %>
+ <% end %>
<%= if show_reward?(@block.rewards) do %>
<%= for block_reward <- @block.rewards do %>
@@ -268,4 +268,4 @@
-<%= render BlockScoutWeb.Advertisement.BannersAdView, "_banner_728.html", conn: @conn %>
+<%= render BlockScoutWeb.Advertisement.BannersAdView, "_banner_728.html", conn: @conn %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex
index 4b84dce35873..f07899ea9bf8 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex
@@ -8,7 +8,7 @@
-
<%= "#{gas_prices_from_oracle["average"]}" <> " " %><%= gettext "Gwei" %>
+
<%= "#{gas_prices_from_oracle[:average]}" <> " " %><%= gettext "Gwei" %>
-
<%= gettext "Slow" %> <%= gas_prices_from_oracle["slow"] %> <%= gettext "Gwei" %>
-
<%= gettext "Average" %> <%= gas_prices_from_oracle["average"] %> <%= gettext "Gwei" %>
-
<%= gettext "Fast" %> <%= gas_prices_from_oracle["fast"] %> <%= gettext "Gwei" %>
+
<%= gettext "Slow" %> <%= gas_prices_from_oracle[:slow] %> <%= gettext "Gwei" %>
+
<%= gettext "Average" %> <%= gas_prices_from_oracle[:average] %> <%= gettext "Gwei" %>
+
<%= gettext "Fast" %> <%= gas_prices_from_oracle[:fast] %> <%= gettext "Gwei" %>
"
>
@@ -40,4 +40,4 @@
<% end %>
<% end %>
-
\ No newline at end of file
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_csv_export_button.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_csv_export_button.html.eex
index 9fd19339cfbe..b0d98d362b41 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_csv_export_button.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_csv_export_button.html.eex
@@ -1,5 +1,7 @@
-
- Download
@address, "type" => @type }) %>><%= gettext("CSV") %>
+<% filter_type = if assigns[:filter_type], do: @filter_type, else: "" %>
+<% filter_value = if assigns[:filter_value], do: @filter_value, else: "" %>
+
+ <%= gettext("Download") %>
@address, "type" => @type, "filter_type" => filter_type, "filter_value" => filter_value }) %>><%= gettext("CSV") %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex
index 9b9f6a88933d..979926300cf6 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex
@@ -10,10 +10,11 @@
<%= gettext "Export Data" %>
-
Export <%= type_display_name(@type) %> for address <%= link(
+ <% filter_text = if Helper.is_valid_filter?(@filter_type, @filter_value, @type), do: " with applied filter by #{@filter_type} (#{@filter_value})", else: "" %>
+
<%= gettext("Export") %> <%= type_display_name(@type) %> <%= gettext("for address") %> <%= link(
@address_hash_string,
to: address_path(@conn, :show, @address_hash_string)
- ) %> to CSV file
+ ) %><%= filter_text %> <%= gettext("to CSV file") %>
@@ -26,7 +27,7 @@
id="export-csv-button"
class="button button-primary"
style="padding: 10px 25px;"
- data-link=<%= address_transaction_path(@conn, type_download_path(@type)) %>
+ data-link=<%= BlockScoutWebController.full_path("/api/v1/#{type_download_path(@type)}") %>
data-address-hash=<%= address_checksum(@address_hash_string) %>
data-type=<%= @type %>
><%= gettext("Download") %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/internal_transaction/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/internal_transaction/_tile.html.eex
index 9880c41c3877..27ede917dd8a 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/internal_transaction/_tile.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/internal_transaction/_tile.html.eex
@@ -33,7 +33,7 @@
to: block_path(BlockScoutWeb.Endpoint, :show, @internal_transaction.block_number)
) %>
-
+
<%= if assigns[:current_address] do %>
<%= if assigns[:current_address].hash == @internal_transaction.from_address_hash do %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex
index 0f82cf278d16..ff88dd8c8fdc 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex
@@ -38,7 +38,7 @@
<% else %>
- "><%= raw(values_with_type(output["value"], output["type"], [output["name"]], 0, output["components"])) %>
+ "><%= raw(values_with_type(output["value"], output["type"], fetch_name(function["names"], index), 0)) %>
<% end %>
<% end %>
<% end %>
@@ -158,4 +158,4 @@
<% end %>
<% end %>
-<% end %>
\ No newline at end of file
+<% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex
index 0d92f45a146b..518c16191172 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex
@@ -1,5 +1,5 @@
<% address_hash = Address.checksum(@token.contract_address_hash) %>
-<% is_proxy = BlockScoutWeb.Tokens.OverviewView.smart_contract_is_proxy?(@token) %>
+<% is_proxy = BlockScoutWeb.Tokens.OverviewView.token_smart_contract_is_proxy?(@token) %>
<%= link(
gettext("Token Transfers"),
@@ -50,4 +50,4 @@
class: "card-tab #{tab_status("write-proxy", @conn.request_path)}")
%>
<% end %>
-
\ No newline at end of file
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex
index d1d151ffe2b5..73b6bbf5afe5 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex
@@ -44,7 +44,7 @@
to: block_path(BlockScoutWeb.Endpoint, :show, @token_transfer.block_number)
) %>
-
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex
index 08a92a4fb370..ebd4a61cadc7 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex
@@ -7,7 +7,7 @@
<% base_fee_per_gas = if block, do: block.base_fee_per_gas, else: nil %>
<% max_priority_fee_per_gas = @transaction.max_priority_fee_per_gas %>
<% max_fee_per_gas = @transaction.max_fee_per_gas %>
-<% burned_fee =
+<% burnt_fees =
if !is_nil(max_fee_per_gas) and !is_nil(@transaction.gas_used) and !is_nil(base_fee_per_gas) do
if Decimal.compare(max_fee_per_gas.value, 0) == :eq do
%Wei{value: Decimal.new(0)}
@@ -17,7 +17,7 @@
else
nil
end %>
-<% %Wei{value: burned_fee_decimal} = if is_nil(burned_fee), do: %Wei{value: Decimal.new(0)}, else: burned_fee %>
+<% %Wei{value: burnt_fee_decimal} = if is_nil(burnt_fees), do: %Wei{value: Decimal.new(0)}, else: burnt_fees %>
<% priority_fee_per_gas = if is_nil(max_priority_fee_per_gas) or is_nil(base_fee_per_gas), do: nil, else: Enum.min_by([max_priority_fee_per_gas, Wei.sub(max_fee_per_gas, base_fee_per_gas)], fn x -> Wei.to(x, :wei) end) %>
<% priority_fee = if is_nil(priority_fee_per_gas), do: nil, else: Wei.mult(priority_fee_per_gas, @transaction.gas_used) %>
<% decoded_input_data = decoded_input_data(@transaction) %>
@@ -122,7 +122,7 @@
<% true -> %>
<%= render BlockScoutWeb.FormView, "_tag.html", text: formatted_status, additional_classes: ["success", "large"] %>
<% end %>
-
+
<%= if confirmations > 0 do %>
<%= gettext "Confirmed by " %><%= confirmations %> <%= " " <> confirmations_ds_name(confirmations) %>
<% end %>
@@ -429,17 +429,17 @@
<%= format_wei_value(priority_fee, :ether) %>
- <% end %>
- <%= if !is_nil(burned_fee) do %>
+ <% end %>
+ <%= if !is_nil(burnt_fees) do %>
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html",
- text: gettext("Amount of") <> " " <> Explorer.coin_name() <> " " <> gettext("burned for this transaction. Equals Block Base Fee per Gas * Gas Used.") %>
+ text: gettext("Amount of") <> " " <> Explorer.coin_name() <> " " <> gettext("burnt for this transaction. Equals Block Base Fee per Gas * Gas Used.") %>
<%= gettext "Transaction Burnt Fee" %>
- <%= format_wei_value(burned_fee, :ether) %>
+ <%= format_wei_value(burnt_fees, :ether) %>
<%= unless empty_exchange_rate?(@exchange_rate) do %>
- ( data-usd-exchange-rate=<%= @exchange_rate.usd_value %>> )
+ ( data-usd-exchange-rate=<%= @exchange_rate.usd_value %>> )
<% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/_state_change.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/_state_change.html.eex
index 85f491f99e02..67a8e63ea389 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/_state_change.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/_state_change.html.eex
@@ -33,10 +33,10 @@
<% end %>
<%= if not_negative?(@balance_before) and not_negative?(@balance_after) do %>
- <%= display_value(@balance_before, coin_or_transfer) %>
+ <%= display_value(@balance_before, coin_or_transfer, @token_id) %>
- <%= display_value(@balance_after, coin_or_transfer) %>
+ <%= display_value(@balance_after, coin_or_transfer, @token_id) %>
<% else %>
@@ -44,20 +44,20 @@
<% end %>
<% end %>
- <%= if is_list(@coin_or_token_transfers) and coin_or_transfer.token.type != "ERC-20" do %>
+ <%= if is_list(@coin_or_token_transfers) and coin_or_transfer.token.type == "ERC-721" do %>
<%= for {type, transfer} <- @coin_or_token_transfers do %>
<%= case type do %>
<% :from -> %>
- ▼ <%= display_nft(transfer) %>
+ ▼ <%= display_erc_721(transfer) %>
<% :to -> %>
- ▲ <%= display_nft(transfer) %>
+ ▲ <%= display_erc_721(transfer) %>
<% end %>
<% end %>
<% else %>
<%= if not_negative?(@balance_diff) do %>
- ▲ <%= display_value(@balance_diff, coin_or_transfer) %>
+ ▲ <%= display_value(@balance_diff, coin_or_transfer, @token_id) %>
<% else %>
- ▼ <%= display_value(absolute_value_of(@balance_diff), coin_or_transfer) %>
+ ▼ <%= display_value(absolute_value_of(@balance_diff), coin_or_transfer, @token_id) %>
<% end %>
<% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/_token_balance.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/_token_balance.html.eex
index 6803a22d4087..17458e27875e 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/_token_balance.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/_token_balance.html.eex
@@ -1 +1,5 @@
-<%= format_according_to_decimals(@balance, @transfer.token.decimals) %><%= " " %><%= render BlockScoutWeb.TransactionView, "_link_to_token_symbol.html", transfer: @transfer %>
+<%= if @token_id do %>
+ <%= BlockScoutWeb.Cldr.Number.to_string!(@balance, format: "#,###") %> <%= render BlockScoutWeb.TransactionView, "_transfer_token_with_id.html", transfer: @transfer, token_id: @token_id %>
+<% else %>
+ <%= format_according_to_decimals(@balance, @transfer.token.decimals) %><%= " " %><%= render BlockScoutWeb.TransactionView, "_link_to_token_symbol.html", transfer: @transfer %>
+<% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/index.html.eex
index c62dbb18bdf5..4e846ff351b1 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/index.html.eex
@@ -4,6 +4,11 @@
<%= render BlockScoutWeb.TransactionView, "_tabs.html", assigns %>
<%= gettext "State changes" %>
+
+
+
<%= gettext("Something went wrong, click to reload.") %>
@@ -46,6 +51,8 @@
<% end %>
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/verified_contracts/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/verified_contracts/index.html.eex
index b8f801b6993b..1149eeab1f3c 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/verified_contracts/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/verified_contracts/index.html.eex
@@ -31,6 +31,12 @@
<%= link(
gettext("Vyper"),
to: Helpers.verified_contracts_path(@conn, :index, filter: "vyper"),
+ class: "address__link address__link--active dropdown-item border-bottom",
+ "data-test": "filter_option"
+ ) %>
+ <%= link(
+ gettext("Yul"),
+ to: Helpers.verified_contracts_path(@conn, :index, filter: "yul"),
class: "address__link address__link--active dropdown-item",
"data-test": "filter_option"
) %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/withdrawal/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/withdrawal/index.html.eex
index fffcff6c721b..33e6809ed697 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/withdrawal/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/withdrawal/index.html.eex
@@ -8,7 +8,7 @@
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: @page_number, show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
- <%= gettext("There are %{withdrawals_count} withdrawals total that withdrawn %{withdrawals_sum}", withdrawals_count: BlockScoutWeb.Cldr.Number.to_string!(@withdrawals_count, format: "#,###"), withdrawals_sum: format_wei_value(@withdrawals_sum, :ether)) %>
+ <%= gettext("%{withdrawals_count} withdrawals processed and %{withdrawals_sum} withdrawn.", withdrawals_count: BlockScoutWeb.Cldr.Number.to_string!(@withdrawals_count, format: "#,###"), withdrawals_sum: format_wei_value(@withdrawals_sum, :ether)) %>
<%= gettext("Something went wrong, click to reload.") %>
diff --git a/apps/block_scout_web/lib/block_scout_web/views/abi_encoded_value_view.ex b/apps/block_scout_web/lib/block_scout_web/views/abi_encoded_value_view.ex
index a4a7fc66bebb..c25dbbf5392e 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/abi_encoded_value_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/abi_encoded_value_view.ex
@@ -192,23 +192,21 @@ defmodule BlockScoutWeb.ABIEncodedValueView do
end
defp base_value_json(_, {:dynamic, value}) do
- hex(value)
+ hex_for_json(value)
end
defp base_value_json(:address, value) do
- hex(value)
- end
-
- defp base_value_json(:address_text, value) do
- hex(value)
+ hex_for_json(value)
end
defp base_value_json(:bytes, value) do
- hex(value)
+ hex_for_json(value)
end
defp base_value_json(_, value), do: to_string(value)
defp hex("0x" <> value), do: "0x" <> value
defp hex(value), do: "0x" <> Base.encode16(value, case: :lower)
+
+ defp hex_for_json(value), do: "0x" <> Base.encode16(value, case: :lower)
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/access_helper.ex b/apps/block_scout_web/lib/block_scout_web/views/access_helper.ex
index d481945f60ce..d0fb8d0c574a 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/access_helper.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/access_helper.ex
@@ -11,7 +11,7 @@ defmodule BlockScoutWeb.AccessHelper do
alias BlockScoutWeb.WebRouter.Helpers
alias Explorer.AccessHelper
alias Explorer.Account.Api.Key, as: ApiKey
- alias Plug.{Conn, Crypto}
+ alias Plug.Conn
alias RemoteIp
@@ -77,7 +77,7 @@ defmodule BlockScoutWeb.AccessHelper do
ip_string = conn_to_ip_string(conn)
plan = get_plan(conn.query_params)
- token = get_ui_v2_token(conn, conn.query_params, ip_string)
+ token = get_ui_v2_token(conn, ip_string)
user_agent = get_user_agent(conn)
@@ -167,18 +167,19 @@ defmodule BlockScoutWeb.AccessHelper do
to_string(:inet_parse.ntoa(ip))
end
- defp get_ui_v2_token(conn, %{"token" => token}, ip_string) do
- case is_api_v2_request?(conn) && Crypto.verify(conn.secret_key_base, conn.secret_key_base, token) do
- {:ok, %{ip: ^ip_string}} ->
- token
+ defp get_ui_v2_token(conn, ip_string) do
+ api_v2_temp_token_key = Application.get_env(:block_scout_web, :api_v2_temp_token_key)
+ conn = Conn.fetch_cookies(conn, signed: [api_v2_temp_token_key])
+
+ case is_api_v2_request?(conn) && conn.cookies[api_v2_temp_token_key] do
+ %{ip: ^ip_string} ->
+ conn.req_cookies[api_v2_temp_token_key]
_ ->
nil
end
end
- defp get_ui_v2_token(_conn, _params, _ip_string), do: nil
-
defp get_user_agent(conn) do
case Conn.get_req_header(conn, "user-agent") do
[agent] ->
diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/user_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/user_view.ex
index b2761714ae90..26c8d7c8295b 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/user_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/user_view.ex
@@ -12,6 +12,17 @@ defmodule BlockScoutWeb.Account.Api.V1.UserView do
%{"name" => identity.name, "email" => identity.email, "avatar" => identity.avatar, "nickname" => identity.nickname}
end
+ def render("watchlist_addresses.json", %{
+ watchlist_addresses: watchlist_addresses,
+ exchange_rate: exchange_rate,
+ next_page_params: next_page_params
+ }) do
+ %{
+ "items" => Enum.map(watchlist_addresses, &prepare_watchlist_address(&1, exchange_rate)),
+ "next_page_params" => next_page_params
+ }
+ end
+
def render("watchlist_addresses.json", %{watchlist_addresses: watchlist_addresses, exchange_rate: exchange_rate}) do
Enum.map(watchlist_addresses, &prepare_watchlist_address(&1, exchange_rate))
end
@@ -20,6 +31,10 @@ defmodule BlockScoutWeb.Account.Api.V1.UserView do
prepare_watchlist_address(watchlist_address, exchange_rate)
end
+ def render("address_tags.json", %{address_tags: address_tags, next_page_params: next_page_params}) do
+ %{"items" => Enum.map(address_tags, &prepare_address_tag/1), "next_page_params" => next_page_params}
+ end
+
def render("address_tags.json", %{address_tags: address_tags}) do
Enum.map(address_tags, &prepare_address_tag/1)
end
@@ -28,6 +43,10 @@ defmodule BlockScoutWeb.Account.Api.V1.UserView do
prepare_address_tag(address_tag)
end
+ def render("transaction_tags.json", %{transaction_tags: transaction_tags, next_page_params: next_page_params}) do
+ %{"items" => Enum.map(transaction_tags, &prepare_transaction_tag/1), "next_page_params" => next_page_params}
+ end
+
def render("transaction_tags.json", %{transaction_tags: transaction_tags}) do
Enum.map(transaction_tags, &prepare_transaction_tag/1)
end
@@ -76,7 +95,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserView do
%{
"id" => watchlist.id,
- "address" => Helper.address_with_info(nil, address, watchlist.address_hash),
+ "address" => Helper.address_with_info(nil, address, watchlist.address_hash, false),
"address_hash" => watchlist.address_hash,
"name" => watchlist.name,
"address_balance" => if(address && address.fetched_coin_balance, do: address.fetched_coin_balance.value),
@@ -102,7 +121,10 @@ defmodule BlockScoutWeb.Account.Api.V1.UserView do
},
"notification_methods" => %{
"email" => watchlist.notify_email
- }
+ },
+ "tokens_fiat_value" => watchlist.tokens_fiat_value,
+ "tokens_count" => watchlist.tokens_count,
+ "tokens_overflow" => watchlist.tokens_overflow
}
end
@@ -112,7 +134,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserView do
%{
"id" => custom_abi.id,
"contract_address_hash" => custom_abi.address_hash,
- "contract_address" => Helper.address_with_info(nil, address, custom_abi.address_hash),
+ "contract_address" => Helper.address_with_info(nil, address, custom_abi.address_hash, false),
"name" => custom_abi.name,
"abi" => custom_abi.abi
}
@@ -128,7 +150,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserView do
%{
"id" => address_tag.id,
"address_hash" => address_tag.address_hash,
- "address" => Helper.address_with_info(nil, address, address_tag.address_hash),
+ "address" => Helper.address_with_info(nil, address, address_tag.address_hash, false),
"name" => address_tag.name
}
end
@@ -142,7 +164,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserView do
def prepare_public_tags_request(public_tags_request) do
addresses =
Enum.map(public_tags_request.addresses, fn address_hash ->
- Helper.address_with_info(nil, get_address(address_hash), address_hash)
+ Helper.address_with_info(nil, get_address(address_hash), address_hash, false)
end)
%{
diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/watchlist_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/watchlist_view.ex
index fa3966323606..c6d513a2bff1 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/account/watchlist_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/account/watchlist_view.ex
@@ -3,7 +3,6 @@ defmodule BlockScoutWeb.Account.WatchlistView do
alias BlockScoutWeb.Account.WatchlistAddressView
alias Explorer.Account.WatchlistAddress
- alias Explorer.ExchangeRates.Token
alias Explorer.Market
alias Indexer.Fetcher.CoinBalanceOnDemand
@@ -12,6 +11,6 @@ defmodule BlockScoutWeb.Account.WatchlistView do
end
def exchange_rate do
- Market.get_exchange_rate(Explorer.coin()) || Token.null()
+ Market.get_coin_exchange_rate()
end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_flattened_code_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_flattened_code_view.ex
index f6e676d7fcdf..e5adf9b9d4ae 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_flattened_code_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_flattened_code_view.ex
@@ -1,7 +1,6 @@
defmodule BlockScoutWeb.AddressContractVerificationViaFlattenedCodeView do
use BlockScoutWeb, :view
- alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.RustVerifierInterface
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_multi_part_files_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_multi_part_files_view.ex
index 12a80e5eb282..76f88f059ac0 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_multi_part_files_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_multi_part_files_view.ex
@@ -1,7 +1,6 @@
defmodule BlockScoutWeb.AddressContractVerificationViaMultiPartFilesView do
use BlockScoutWeb, :view
- alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.RustVerifierInterface
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_standard_json_input_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_standard_json_input_view.ex
index cf45efe96f0a..9a4298a01c4b 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_standard_json_input_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_standard_json_input_view.ex
@@ -1,6 +1,5 @@
defmodule BlockScoutWeb.AddressContractVerificationViaStandardJsonInputView do
use BlockScoutWeb, :view
- alias Explorer.Chain
alias Explorer.Chain.SmartContract
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_vyper_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_vyper_view.ex
index e0ebba9694a1..47da5eab9093 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_vyper_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_vyper_view.ex
@@ -1,6 +1,5 @@
defmodule BlockScoutWeb.AddressContractVerificationVyperView do
use BlockScoutWeb, :view
- alias Explorer.Chain
alias Explorer.Chain.SmartContract
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex
index 9780d2a023a0..01b13e6a71d3 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex
@@ -1,10 +1,15 @@
defmodule BlockScoutWeb.AddressContractView do
use BlockScoutWeb, :view
- alias ABI.{FunctionSelector, TypeDecoder}
+ require Logger
+
+ import Explorer.Helper, only: [decode_data: 2]
+
+ alias ABI.FunctionSelector
alias Explorer.Chain
alias Explorer.Chain.{Address, Data, InternalTransaction, Transaction}
- alias Explorer.SmartContract.Helper
+ alias Explorer.Chain.SmartContract
+ alias Explorer.Chain.SmartContract.Proxy.EIP1167
def render("scripts.html", %{conn: conn}) do
render_scripts(conn, "address_contract/code_highlighting.js")
@@ -34,9 +39,12 @@ defmodule BlockScoutWeb.AddressContractView do
|> decode_data(input_types)
|> Enum.zip(constructor_abi["inputs"])
|> Enum.reduce({0, "#{contract.constructor_arguments}\n\n"}, fn {val, %{"type" => type}}, {count, acc} ->
- formatted_val = Helper.sanitize_input(val_to_string(val, type, conn))
+ formatted_val = val_to_string(val, type, conn)
- {count + 1, "#{acc}Arg [#{count}] (#{Helper.sanitize_input(type)} ) : #{formatted_val}\n"}
+ {count + 1,
+ ~E"""
+ <%= acc %>Arg [<%= count %>] (<%= type %> ) : <%= formatted_val %>
+ """}
end)
result
@@ -82,27 +90,19 @@ defmodule BlockScoutWeb.AddressContractView do
defp get_formatted_address_data(address, address_hash, conn) do
if address != nil do
- "" <> address_hash <> " "
+ ~E"><%= address_hash %> "
else
address_hash
end
end
- def decode_data("0x" <> encoded_data, types) do
- decode_data(encoded_data, types)
- end
-
- def decode_data(encoded_data, types) do
- encoded_data
- |> Base.decode16!(case: :mixed)
- |> TypeDecoder.decode_raw(types)
- end
-
def format_external_libraries(libraries, conn) do
Enum.reduce(libraries, "", fn %{name: name, address_hash: address_hash}, acc ->
address = get_address(address_hash)
- "#{acc}#{Helper.sanitize_input(name)} : #{get_formatted_address_data(address, address_hash, conn)} \n"
+ ~E"""
+ <%= acc %><%= name %> : <%= get_formatted_address_data(address, address_hash, conn) %>
+ """
end)
end
@@ -134,6 +134,12 @@ defmodule BlockScoutWeb.AddressContractView do
chain_id = Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:chain_id]
repo_url = Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:repo_url]
match = if partial_match, do: "/partial_match/", else: "/full_match/"
- repo_url <> match <> chain_id <> "/" <> checksummed_hash <> "/"
+
+ if chain_id do
+ repo_url <> match <> chain_id <> "/" <> checksummed_hash <> "/"
+ else
+ Logger.warning("chain_id is nil. Please set CHAIN_ID env variable.")
+ nil
+ end
end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex
index 577661ab8161..0c4daa067d93 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex
@@ -1,9 +1,7 @@
defmodule BlockScoutWeb.AddressLogsView do
use BlockScoutWeb, :view
- alias Explorer.Chain.{Address, Log}
+ alias Explorer.Chain.Address
- def decode(log, transaction) do
- Log.decode(log, transaction)
- end
+ import BlockScoutWeb.AddressView, only: [decode: 2]
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_token_balance_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_token_balance_view.ex
index 96d6153074e8..f05d049412ee 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/address_token_balance_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/address_token_balance_view.ex
@@ -11,7 +11,7 @@ defmodule BlockScoutWeb.AddressTokenBalanceView do
end
def filter_by_type(token_balances, type) do
- Enum.filter(token_balances, fn {_token_balance, token} -> token.type == type end)
+ Enum.filter(token_balances, fn token_balance -> token_balance.token.type == type end)
end
def address_tokens_usd_sum_cache(address, token_balances) do
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex
index f521d4d9032c..ed3f54040657 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex
@@ -6,8 +6,10 @@ defmodule BlockScoutWeb.AddressView do
alias BlockScoutWeb.{AccessHelper, LayoutView}
alias Explorer.Account.CustomABI
alias Explorer.{Chain, CustomContractsHelper, Repo}
- alias Explorer.Chain.{Address, Hash, InternalTransaction, SmartContract, Token, TokenTransfer, Transaction, Wei}
+ alias Explorer.Chain.Address.Counters
+ alias Explorer.Chain.{Address, Hash, InternalTransaction, Log, SmartContract, Token, TokenTransfer, Transaction, Wei}
alias Explorer.Chain.Block.Reward
+ alias Explorer.Chain.SmartContract.Proxy
alias Explorer.ExchangeRates.Token, as: TokenExchangeRate
alias Explorer.SmartContract.{Helper, Writer}
@@ -198,7 +200,7 @@ defmodule BlockScoutWeb.AddressView do
def primary_name(%Address{names: _} = address) do
with false <- is_nil(address.contract_code),
- twin <- Chain.get_verified_twin_contract(address),
+ twin <- SmartContract.get_verified_twin_contract(address),
false <- is_nil(twin) do
twin.name
else
@@ -257,17 +259,17 @@ defmodule BlockScoutWeb.AddressView do
Enum.any?(address.smart_contract.abi || [], &is_read_function?(&1))
end
- def smart_contract_with_read_only_functions?(%Address{smart_contract: nil}), do: false
+ def smart_contract_with_read_only_functions?(%Address{smart_contract: _}), do: false
def is_read_function?(function), do: Helper.queriable_method?(function) || Helper.read_with_wallet_method?(function)
def smart_contract_is_proxy?(address, options \\ [])
def smart_contract_is_proxy?(%Address{smart_contract: %SmartContract{} = smart_contract}, options) do
- SmartContract.proxy_contract?(smart_contract, options)
+ Proxy.proxy_contract?(smart_contract, options)
end
- def smart_contract_is_proxy?(%Address{smart_contract: nil}, _), do: false
+ def smart_contract_is_proxy?(%Address{smart_contract: _}, _), do: false
def smart_contract_with_write_functions?(%Address{smart_contract: %SmartContract{}} = address) do
!contract_interaction_disabled?() &&
@@ -277,7 +279,7 @@ defmodule BlockScoutWeb.AddressView do
)
end
- def smart_contract_with_write_functions?(%Address{smart_contract: nil}), do: false
+ def smart_contract_with_write_functions?(%Address{smart_contract: _}), do: false
def has_decompiled_code?(address) do
address.has_decompiled_code? ||
@@ -455,7 +457,7 @@ defmodule BlockScoutWeb.AddressView do
end
def smart_contract_is_gnosis_safe_proxy?(%Address{smart_contract: %SmartContract{}} = address) do
- address.smart_contract.name == "GnosisSafeProxy" && Chain.gnosis_safe_contract?(address.smart_contract.abi)
+ address.smart_contract.name == "GnosisSafeProxy" && Proxy.gnosis_safe_contract?(address.smart_contract.abi)
end
def smart_contract_is_gnosis_safe_proxy?(_address), do: false
@@ -494,4 +496,17 @@ defmodule BlockScoutWeb.AddressView do
do: !is_nil(custom_abi) && Enum.any?(custom_abi.abi, &Writer.write_function?(&1))
def contract_interaction_disabled?, do: Application.get_env(:block_scout_web, :contract)[:disable_interaction]
+
+ @doc """
+ Decodes given log
+ """
+ @spec decode(Log.t(), Transaction.t()) ::
+ {:ok, String.t(), String.t(), map()}
+ | {:error, atom()}
+ | {:error, atom(), list()}
+ | {{:error, :contract_not_verified, list()}, any()}
+ def decode(log, transaction) do
+ {result, _contracts_acc, _events_acc} = Log.decode(log, transaction, [], true)
+ result
+ end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
index 2d9deed6efb4..263462e9cc54 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
@@ -113,7 +113,7 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
"to" => "#{transaction.to_address_hash}",
"value" => "#{transaction.value.value}",
"gas" => "#{transaction.gas}",
- "gasPrice" => "#{transaction.gas_price.value}",
+ "gasPrice" => "#{transaction.gas_price && transaction.gas_price.value}",
"isError" => if(transaction.status == :ok, do: "0", else: "1"),
"txreceipt_status" => if(transaction.status == :ok, do: "1", else: "0"),
"input" => "#{transaction.input}",
@@ -160,7 +160,7 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
"tokenDecimal" => to_string(token_transfer.token_decimals),
"transactionIndex" => to_string(token_transfer.transaction_index),
"gas" => to_string(token_transfer.transaction_gas),
- "gasPrice" => to_string(token_transfer.transaction_gas_price.value),
+ "gasPrice" => to_string(token_transfer.transaction_gas_price && token_transfer.transaction_gas_price.value),
"gasUsed" => to_string(token_transfer.transaction_gas_used),
"cumulativeGasUsed" => to_string(token_transfer.transaction_cumulative_gas_used),
"input" => to_string(token_transfer.transaction_input),
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex
index d1686cef1565..bc47f2bd85cc 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex
@@ -4,7 +4,6 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
alias BlockScoutWeb.AddressView
alias BlockScoutWeb.API.RPC.RPCView
alias Ecto.Association.NotLoaded
- alias Explorer.Chain
alias Explorer.Chain.{Address, DecompiledSmartContract, SmartContract}
defguardp is_empty_string(input) when input == "" or input == nil
@@ -168,7 +167,7 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
end
defp insert_additional_sources(output, address) do
- additional_sources_from_twin = Chain.get_address_verified_twin_contract(address.hash).additional_sources
+ additional_sources_from_twin = SmartContract.get_address_verified_twin_contract(address.hash).additional_sources
additional_sources =
if AddressView.smart_contract_verified?(address),
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/logs_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/logs_view.ex
index 6f2933d7c63b..f52ab8fadccd 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/logs_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/logs_view.ex
@@ -44,6 +44,8 @@ defmodule BlockScoutWeb.API.RPC.LogsView do
|> integer_to_hex()
end
+ defp datetime_to_hex(nil), do: nil
+
defp datetime_to_hex(datetime) do
datetime
|> DateTime.to_unix()
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex
index 4b81b110914f..4a18643aa354 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex
@@ -2,6 +2,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do
use BlockScoutWeb, :view
alias BlockScoutWeb.API.RPC.RPCView
+ alias Explorer.Chain.Transaction
def render("gettxinfo.json", %{
transaction: transaction,
@@ -58,7 +59,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do
defp prepare_transaction(transaction, block_height, logs, next_page_params) do
%{
"hash" => "#{transaction.hash}",
- "timeStamp" => "#{DateTime.to_unix(transaction.block.timestamp)}",
+ "timeStamp" => "#{DateTime.to_unix(Transaction.block_timestamp(transaction))}",
"blockNumber" => "#{transaction.block_number}",
"confirmations" => "#{block_height - transaction.block_number}",
"success" => if(transaction.status == :ok, do: true, else: false),
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex
index e044cf7ffb6e..1b29a23d4b73 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex
@@ -7,8 +7,9 @@ defmodule BlockScoutWeb.API.V2.AddressView do
alias BlockScoutWeb.API.V2.{ApiView, Helper, TokenView}
alias BlockScoutWeb.API.V2.Helper
alias Explorer.{Chain, Market}
+ alias Explorer.Chain.Address.Counters
alias Explorer.Chain.{Address, SmartContract}
- alias Explorer.ExchangeRates.Token
+ alias Explorer.Chain.Token.Instance
@api_true [api?: true]
@@ -54,20 +55,45 @@ defmodule BlockScoutWeb.API.V2.AddressView do
}
end
- def prepare_address({address, nonce}) do
+ def render("nft_list.json", %{token_instances: token_instances, token: token, next_page_params: next_page_params}) do
+ %{"items" => Enum.map(token_instances, &prepare_nft(&1, token)), "next_page_params" => next_page_params}
+ end
+
+ def render("nft_list.json", %{token_instances: token_instances, next_page_params: next_page_params}) do
+ %{"items" => Enum.map(token_instances, &prepare_nft(&1)), "next_page_params" => next_page_params}
+ end
+
+ def render("nft_collections.json", %{collections: nft_collections, next_page_params: next_page_params}) do
+ %{"items" => Enum.map(nft_collections, &prepare_nft_collection(&1)), "next_page_params" => next_page_params}
+ end
+
+ @spec prepare_address(
+ {atom() | %{:fetched_coin_balance => any(), :hash => any(), optional(any()) => any()}, any()}
+ | Explorer.Chain.Address.t()
+ ) :: %{optional(:coin_balance) => any(), optional(:tx_count) => binary(), optional(<<_::32, _::_*8>>) => any()}
+ def prepare_address({address, tx_count}) do
nil
- |> Helper.address_with_info(address, address.hash)
- |> Map.put(:tx_count, to_string(nonce))
+ |> Helper.address_with_info(address, address.hash, true)
+ |> Map.put(:tx_count, to_string(tx_count))
|> Map.put(:coin_balance, if(address.fetched_coin_balance, do: address.fetched_coin_balance.value))
end
def prepare_address(address, conn \\ nil) do
- base_info = Helper.address_with_info(conn, address, address.hash)
- is_proxy = AddressView.smart_contract_is_proxy?(address, @api_true)
+ base_info = Helper.address_with_info(conn, address, address.hash, true)
+
+ {:ok, address_with_smart_contract} =
+ Chain.hash_to_address(
+ address.hash,
+ [necessity_by_association: %{:smart_contract => :optional}],
+ false
+ )
+
+ is_proxy = AddressView.smart_contract_is_proxy?(address_with_smart_contract, @api_true)
{implementation_address, implementation_name} =
with true <- is_proxy,
- {address, name} <- SmartContract.get_implementation_address_hash(address.smart_contract, @api_true),
+ {address, name} <-
+ SmartContract.get_implementation_address_hash(address_with_smart_contract.smart_contract, @api_true),
false <- is_nil(address),
{:ok, address_hash} <- Chain.string_to_address_hash(address),
checksummed_address <- Address.checksum(address_hash) do
@@ -78,7 +104,7 @@ defmodule BlockScoutWeb.API.V2.AddressView do
end
balance = address.fetched_coin_balance && address.fetched_coin_balance.value
- exchange_rate = (Market.get_exchange_rate(Explorer.coin()) || Token.null()).usd_value
+ exchange_rate = Market.get_coin_exchange_rate().usd_value
creator_hash = AddressView.from_address_hash(address)
creation_tx = creator_hash && AddressView.transaction_hash(address)
@@ -101,29 +127,31 @@ defmodule BlockScoutWeb.API.V2.AddressView do
"has_methods_read" => AddressView.smart_contract_with_read_only_functions?(address),
"has_methods_write" => AddressView.smart_contract_with_write_functions?(address),
"has_methods_read_proxy" => is_proxy,
- "has_methods_write_proxy" => AddressView.smart_contract_with_write_functions?(address) && is_proxy,
+ "has_methods_write_proxy" =>
+ AddressView.smart_contract_with_write_functions?(address_with_smart_contract) && is_proxy,
"has_decompiled_code" => AddressView.has_decompiled_code?(address),
- "has_validated_blocks" => Chain.check_if_validated_blocks_at_address(address.hash, @api_true),
- "has_logs" => Chain.check_if_logs_at_address(address.hash, @api_true),
- "has_tokens" => Chain.check_if_tokens_at_address(address.hash, @api_true),
- "has_token_transfers" => Chain.check_if_token_transfers_at_address(address.hash, @api_true),
+ "has_validated_blocks" => Counters.check_if_validated_blocks_at_address(address.hash, @api_true),
+ "has_logs" => Counters.check_if_logs_at_address(address.hash, @api_true),
+ "has_tokens" => Counters.check_if_tokens_at_address(address.hash, @api_true),
+ "has_token_transfers" => Counters.check_if_token_transfers_at_address(address.hash, @api_true),
"watchlist_address_id" => Chain.select_watchlist_address_id(get_watchlist_id(conn), address.hash),
- "has_beacon_chain_withdrawals" => Chain.check_if_withdrawals_at_address(address.hash, @api_true)
+ "has_beacon_chain_withdrawals" => Counters.check_if_withdrawals_at_address(address.hash, @api_true)
})
end
- def prepare_token_balance({token_balance, token}, fetch_token_instance? \\ false) do
+ def prepare_token_balance(token_balance, fetch_token_instance? \\ false) do
%{
"value" => token_balance.value,
- "token" => TokenView.render("token.json", %{token: token}),
+ "token" => TokenView.render("token.json", %{token: token_balance.token}),
"token_id" => token_balance.token_id,
"token_instance" =>
if(fetch_token_instance? && token_balance.token_id,
do:
fetch_and_render_token_instance(
token_balance.token_id,
- token,
- token_balance.address_hash
+ token_balance.token,
+ token_balance.address_hash,
+ token_balance
)
)
}
@@ -156,7 +184,44 @@ defmodule BlockScoutWeb.API.V2.AddressView do
end
end
- def fetch_and_render_token_instance(token_id, token, address_hash) do
+ defp prepare_nft(nft) do
+ prepare_nft(nft, nft.token)
+ end
+
+ defp prepare_nft(nft, token) do
+ Map.merge(
+ %{"token_type" => token.type, "value" => value(token.type, nft)},
+ TokenView.prepare_token_instance(nft, token)
+ )
+ end
+
+ defp prepare_nft_collection(collection) do
+ %{
+ "token" => TokenView.render("token.json", token: collection.token),
+ "amount" => string_or_null(collection.distinct_token_instances_count || collection.value),
+ "token_instances" =>
+ Enum.map(collection.preloaded_token_instances, fn instance ->
+ prepare_nft_for_collection(collection.token.type, instance)
+ end)
+ }
+ end
+
+ defp prepare_nft_for_collection(token_type, instance) do
+ Map.merge(
+ %{"token_type" => token_type, "value" => value(token_type, instance)},
+ TokenView.prepare_token_instance(instance, nil)
+ )
+ end
+
+ defp value("ERC-721", _), do: "1"
+ defp value(_, nft), do: nft.current_token_balance && to_string(nft.current_token_balance.value)
+
+ defp string_or_null(nil), do: nil
+ defp string_or_null(other), do: to_string(other)
+
+ # TODO think about this approach mb refactor or mark deprecated for example.
+ # Suggested solution: batch preload
+ def fetch_and_render_token_instance(token_id, token, address_hash, token_balance) do
token_instance =
case Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(
token_id,
@@ -164,10 +229,13 @@ defmodule BlockScoutWeb.API.V2.AddressView do
@api_true
) do
# `%{hash: address_hash}` will match with `address_with_info(_, address_hash)` clause in `BlockScoutWeb.API.V2.Helper`
- {:ok, token_instance} -> %{token_instance | owner: %{hash: address_hash}}
- {:error, :not_found} -> %{token_id: token_id, metadata: nil, owner: %{hash: address_hash}}
+ {:ok, token_instance} -> %Instance{token_instance | owner: %{hash: address_hash}}
+ {:error, :not_found} -> %Instance{token_id: token_id, metadata: nil, owner: %{hash: address_hash}}
end
- TokenView.render("token_instance.json", %{token_instance: token_instance, token: token})
+ TokenView.render("token_instance.json", %{
+ token_instance: %Instance{token_instance | current_token_balance: token_balance},
+ token: token
+ })
end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex
index 3a9e914400b8..9c4b40a3516f 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex
@@ -3,12 +3,9 @@ defmodule BlockScoutWeb.API.V2.BlockView do
alias BlockScoutWeb.BlockView
alias BlockScoutWeb.API.V2.{ApiView, Helper}
- alias Explorer.Chain
alias Explorer.Chain.Block
alias Explorer.Counters.BlockPriorityFeeCounter
- @api_true [api?: true]
-
def render("message.json", assigns) do
ApiView.render("message.json", assigns)
end
@@ -31,16 +28,16 @@ defmodule BlockScoutWeb.API.V2.BlockView do
end
def prepare_block(block, _conn, single_block? \\ false) do
- burned_fee = Chain.burned_fees(block.transactions, block.base_fee_per_gas)
+ burnt_fees = Block.burnt_fees(block.transactions, block.base_fee_per_gas)
priority_fee = block.base_fee_per_gas && BlockPriorityFeeCounter.fetch(block.hash)
- tx_fees = Chain.txn_fees(block.transactions)
+ transaction_fees = Block.transaction_fees(block.transactions)
%{
"height" => block.number,
"timestamp" => block.timestamp,
"tx_count" => count_transactions(block),
- "miner" => Helper.address_with_info(block.miner, block.miner_hash),
+ "miner" => Helper.address_with_info(nil, block.miner, block.miner_hash, false),
"size" => block.size,
"hash" => block.hash,
"parent_hash" => block.parent_hash,
@@ -50,20 +47,20 @@ defmodule BlockScoutWeb.API.V2.BlockView do
"gas_limit" => block.gas_limit,
"nonce" => block.nonce,
"base_fee_per_gas" => block.base_fee_per_gas,
- "burnt_fees" => burned_fee,
+ "burnt_fees" => burnt_fees,
"priority_fee" => priority_fee,
- "extra_data" => "TODO",
+ # "extra_data" => "TODO",
"uncles_hashes" => prepare_uncles(block.uncle_relations),
- "state_root" => "TODO",
+ # "state_root" => "TODO",
"rewards" => prepare_rewards(block.rewards, block, single_block?),
"gas_target_percentage" => gas_target(block),
"gas_used_percentage" => gas_used_percentage(block),
- "burnt_fees_percentage" => burnt_fees_percentage(burned_fee, tx_fees),
+ "burnt_fees_percentage" => burnt_fees_percentage(burnt_fees, transaction_fees),
"type" => block |> BlockView.block_type() |> String.downcase(),
- "tx_fees" => tx_fees,
- "has_beacon_chain_withdrawals" =>
- if(single_block?, do: Chain.check_if_withdrawals_in_block(block.hash, @api_true), else: nil)
+ "tx_fees" => transaction_fees,
+ "withdrawals_count" => count_withdrawals(block)
}
+ |> chain_type_fields(block, single_block?)
end
def prepare_rewards(rewards, block, single_block?) do
@@ -88,23 +85,50 @@ defmodule BlockScoutWeb.API.V2.BlockView do
end
def gas_target(block) do
- elasticity_multiplier = Application.get_env(:explorer, :elasticity_multiplier)
- ratio = Decimal.div(block.gas_used, Decimal.div(block.gas_limit, elasticity_multiplier))
- ratio |> Decimal.sub(1) |> Decimal.mult(100) |> Decimal.to_float()
+ if Decimal.compare(block.gas_limit, 0) == :gt do
+ elasticity_multiplier = Application.get_env(:explorer, :elasticity_multiplier)
+ ratio = Decimal.div(block.gas_used, Decimal.div(block.gas_limit, elasticity_multiplier))
+ ratio |> Decimal.sub(1) |> Decimal.mult(100) |> Decimal.to_float()
+ else
+ Decimal.new(0)
+ end
end
def gas_used_percentage(block) do
- block.gas_used |> Decimal.div(block.gas_limit) |> Decimal.mult(100) |> Decimal.to_float()
+ if Decimal.compare(block.gas_limit, 0) == :gt do
+ block.gas_used |> Decimal.div(block.gas_limit) |> Decimal.mult(100) |> Decimal.to_float()
+ else
+ Decimal.new(0)
+ end
end
def burnt_fees_percentage(_, %Decimal{coef: 0}), do: nil
- def burnt_fees_percentage(burnt_fees, tx_fees) when not is_nil(tx_fees) and not is_nil(burnt_fees) do
- burnt_fees.value |> Decimal.div(tx_fees) |> Decimal.mult(100) |> Decimal.to_float()
+ def burnt_fees_percentage(burnt_fees, transaction_fees)
+ when not is_nil(transaction_fees) and not is_nil(burnt_fees) do
+ burnt_fees.value |> Decimal.div(transaction_fees) |> Decimal.mult(100) |> Decimal.to_float()
end
def burnt_fees_percentage(_, _), do: nil
def count_transactions(%Block{transactions: txs}) when is_list(txs), do: Enum.count(txs)
def count_transactions(_), do: nil
+
+ def count_withdrawals(%Block{withdrawals: withdrawals}) when is_list(withdrawals), do: Enum.count(withdrawals)
+ def count_withdrawals(_), do: nil
+
+ defp chain_type_fields(result, block, single_block?) do
+ case single_block? && Application.get_env(:explorer, :chain_type) do
+ "rsk" ->
+ result
+ |> Map.put("minimum_gas_price", block.minimum_gas_price)
+ |> Map.put("bitcoin_merged_mining_header", block.bitcoin_merged_mining_header)
+ |> Map.put("bitcoin_merged_mining_coinbase_transaction", block.bitcoin_merged_mining_coinbase_transaction)
+ |> Map.put("bitcoin_merged_mining_merkle_proof", block.bitcoin_merged_mining_merkle_proof)
+ |> Map.put("hash_for_merged_mining", block.hash_for_merged_mining)
+
+ _ ->
+ result
+ end
+ end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex
index 1c427c921865..96a69840ee24 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex
@@ -10,13 +10,13 @@ defmodule BlockScoutWeb.API.V2.Helper do
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 3]
- def address_with_info(conn, address, address_hash, watchlist_names_cached \\ nil)
+ def address_with_info(conn, address, address_hash, tags_needed?, watchlist_names_cached \\ nil)
- def address_with_info(_, _, nil, _) do
+ def address_with_info(_, _, nil, _, _) do
nil
end
- def address_with_info(conn, address, address_hash, nil) do
+ def address_with_info(conn, address, address_hash, true, nil) do
%{
common_tags: public_tags,
personal_tags: private_tags,
@@ -30,7 +30,15 @@ defmodule BlockScoutWeb.API.V2.Helper do
})
end
- def address_with_info(_conn, address, address_hash, watchlist_names_cached) do
+ def address_with_info(_conn, address, address_hash, false, nil) do
+ Map.merge(address_with_info(address, address_hash), %{
+ "private_tags" => [],
+ "watchlist_names" => [],
+ "public_tags" => []
+ })
+ end
+
+ def address_with_info(_conn, address, address_hash, _, watchlist_names_cached) do
watchlist_name = watchlist_names_cached[address_hash]
Map.merge(address_with_info(address, address_hash), %{
@@ -40,25 +48,32 @@ defmodule BlockScoutWeb.API.V2.Helper do
})
end
- def address_with_info(%Address{} = address, _address_hash) do
+ defp address_with_info(%Address{} = address, _address_hash) do
%{
"hash" => Address.checksum(address),
- "is_contract" => is_smart_contract(address),
+ "is_contract" => Address.is_smart_contract(address),
"name" => address_name(address),
"implementation_name" => implementation_name(address),
- "is_verified" => is_verified(address)
+ "is_verified" => is_verified(address),
+ "ens_domain_name" => address.ens_domain_name
}
end
- def address_with_info(%NotLoaded{}, address_hash) do
+ defp address_with_info(%{ens_domain_name: name}, address_hash) do
+ nil
+ |> address_with_info(address_hash)
+ |> Map.put("ens_domain_name", name)
+ end
+
+ defp address_with_info(%NotLoaded{}, address_hash) do
address_with_info(nil, address_hash)
end
- def address_with_info(nil, nil) do
+ defp address_with_info(nil, nil) do
nil
end
- def address_with_info(_, address_hash) do
+ defp address_with_info(_, address_hash) do
%{
"hash" => Address.checksum(address_hash),
"is_contract" => false,
@@ -86,29 +101,20 @@ defmodule BlockScoutWeb.API.V2.Helper do
def implementation_name(_), do: nil
- def is_smart_contract(%Address{contract_code: nil}), do: false
- def is_smart_contract(%Address{contract_code: _}), do: true
- def is_smart_contract(%NotLoaded{}), do: nil
- def is_smart_contract(_), do: false
-
def is_verified(%Address{smart_contract: nil}), do: false
def is_verified(%Address{smart_contract: %{metadata_from_verified_twin: true}}), do: false
def is_verified(%Address{smart_contract: %NotLoaded{}}), do: nil
def is_verified(%Address{smart_contract: _}), do: true
- def market_cap(:standard, %{available_supply: available_supply, usd_value: usd_value})
+ def market_cap(:standard, %{available_supply: available_supply, usd_value: usd_value, market_cap_usd: market_cap_usd})
when is_nil(available_supply) or is_nil(usd_value) do
- Decimal.new(0)
+ max(Decimal.new(0), market_cap_usd)
end
def market_cap(:standard, %{available_supply: available_supply, usd_value: usd_value}) do
Decimal.mult(available_supply, usd_value)
end
- def market_cap(:standard, exchange_rate) do
- exchange_rate.market_cap_usd
- end
-
def market_cap(module, exchange_rate) do
module.market_cap(exchange_rate)
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_edge_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_edge_view.ex
new file mode 100644
index 000000000000..3a813a6a1960
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_edge_view.ex
@@ -0,0 +1,50 @@
+defmodule BlockScoutWeb.API.V2.PolygonEdgeView do
+ use BlockScoutWeb, :view
+
+ @spec render(String.t(), map()) :: map()
+ def render("polygon_edge_deposits.json", %{
+ deposits: deposits,
+ next_page_params: next_page_params
+ }) do
+ %{
+ items:
+ Enum.map(deposits, fn deposit ->
+ %{
+ "msg_id" => deposit.msg_id,
+ "from" => deposit.from,
+ "to" => deposit.to,
+ "l1_transaction_hash" => deposit.l1_transaction_hash,
+ "l1_timestamp" => deposit.l1_timestamp,
+ "success" => deposit.success,
+ "l2_transaction_hash" => deposit.l2_transaction_hash
+ }
+ end),
+ next_page_params: next_page_params
+ }
+ end
+
+ def render("polygon_edge_withdrawals.json", %{
+ withdrawals: withdrawals,
+ next_page_params: next_page_params
+ }) do
+ %{
+ items:
+ Enum.map(withdrawals, fn withdrawal ->
+ %{
+ "msg_id" => withdrawal.msg_id,
+ "from" => withdrawal.from,
+ "to" => withdrawal.to,
+ "l2_transaction_hash" => withdrawal.l2_transaction_hash,
+ "l2_timestamp" => withdrawal.l2_timestamp,
+ "success" => withdrawal.success,
+ "l1_transaction_hash" => withdrawal.l1_transaction_hash
+ }
+ end),
+ next_page_params: next_page_params
+ }
+ end
+
+ def render("polygon_edge_items_count.json", %{count: count}) do
+ count
+ end
+end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex
index bddd3b60d9ef..b56c67352056 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex
@@ -1,13 +1,18 @@
defmodule BlockScoutWeb.API.V2.SearchView do
use BlockScoutWeb, :view
- alias BlockScoutWeb.Endpoint
- alias Explorer.Chain.{Address, Block, Transaction}
+ alias BlockScoutWeb.{BlockView, Endpoint}
+ alias Explorer.Chain
+ alias Explorer.Chain.{Address, Block, Hash, Transaction}
def render("search_results.json", %{search_results: search_results, next_page_params: next_page_params}) do
%{"items" => Enum.map(search_results, &prepare_search_result/1), "next_page_params" => next_page_params}
end
+ def render("search_results.json", %{search_results: search_results}) do
+ Enum.map(search_results, &prepare_search_result/1)
+ end
+
def render("search_results.json", %{result: {:ok, result}}) do
Map.merge(%{"redirect" => true}, redirect_search_results(result))
end
@@ -23,28 +28,48 @@ defmodule BlockScoutWeb.API.V2.SearchView do
"symbol" => search_result.symbol,
"address" => search_result.address_hash,
"token_url" => token_path(Endpoint, :show, search_result.address_hash),
- "address_url" => address_path(Endpoint, :show, search_result.address_hash)
+ "address_url" => address_path(Endpoint, :show, search_result.address_hash),
+ "icon_url" => search_result.icon_url,
+ "token_type" => search_result.token_type,
+ "is_smart_contract_verified" => search_result.verified,
+ "exchange_rate" => search_result.exchange_rate && to_string(search_result.exchange_rate),
+ "total_supply" => search_result.total_supply,
+ "circulating_market_cap" =>
+ search_result.circulating_market_cap && to_string(search_result.circulating_market_cap),
+ "is_verified_via_admin_panel" => search_result.is_verified_via_admin_panel
}
end
- def prepare_search_result(%{type: address_or_contract} = search_result)
- when address_or_contract in ["address", "contract"] do
+ def prepare_search_result(%{type: address_or_contract_or_label} = search_result)
+ when address_or_contract_or_label in ["address", "contract", "label"] do
%{
"type" => search_result.type,
"name" => search_result.name,
"address" => search_result.address_hash,
- "url" => address_path(Endpoint, :show, search_result.address_hash)
+ "url" => address_path(Endpoint, :show, search_result.address_hash),
+ "is_smart_contract_verified" => search_result.verified,
+ "ens_info" => search_result[:ens_info]
}
end
def prepare_search_result(%{type: "block"} = search_result) do
block_hash = hash_to_string(search_result.block_hash)
+ {:ok, block} =
+ Chain.hash_to_block(hash(search_result.block_hash),
+ necessity_by_association: %{
+ :nephews => :optional
+ },
+ api?: true
+ )
+
%{
"type" => search_result.type,
"block_number" => search_result.block_number,
"block_hash" => block_hash,
- "url" => block_path(Endpoint, :show, block_hash)
+ "url" => block_path(Endpoint, :show, block_hash),
+ "timestamp" => search_result.timestamp,
+ "block_type" => block |> BlockView.block_type() |> String.downcase()
}
end
@@ -54,12 +79,22 @@ defmodule BlockScoutWeb.API.V2.SearchView do
%{
"type" => search_result.type,
"tx_hash" => tx_hash,
- "url" => transaction_path(Endpoint, :show, tx_hash)
+ "url" => transaction_path(Endpoint, :show, tx_hash),
+ "timestamp" => search_result.timestamp
}
end
+ defp hash_to_string(%Hash{bytes: bytes}), do: hash_to_string(bytes)
defp hash_to_string(hash), do: "0x" <> Base.encode16(hash, case: :lower)
+ defp hash(%Hash{} = hash), do: hash
+
+ defp hash(bytes),
+ do: %Hash{
+ byte_count: 32,
+ bytes: bytes
+ }
+
defp redirect_search_results(%Address{} = item) do
%{"type" => "address", "parameter" => Address.checksum(item.hash)}
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex
index bfe8f1d09d1d..868536acfbbe 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex
@@ -1,6 +1,9 @@
defmodule BlockScoutWeb.API.V2.SmartContractView do
use BlockScoutWeb, :view
+ import Explorer.Helper, only: [decode_data: 2]
+ import Explorer.SmartContract.Reader, only: [zip_tuple_values_with_types: 2]
+
alias ABI.FunctionSelector
alias BlockScoutWeb.API.V2.{Helper, TransactionView}
alias BlockScoutWeb.SmartContractView
@@ -8,6 +11,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
alias Ecto.Changeset
alias Explorer.Chain
alias Explorer.Chain.{Address, SmartContract}
+ alias Explorer.Chain.SmartContract.Proxy.EIP1167
alias Explorer.Visualize.Sol2uml
require Logger
@@ -98,7 +102,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
%{result: %{error: error}, is_error: true}
_ ->
- %{result: %{output: outputs, names: names}, is_error: false}
+ %{result: %{output: Enum.map(outputs, &render_json/1), names: names}, is_error: false}
end
end
@@ -118,25 +122,25 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
function
|> Map.drop(["abi_outputs"])
- outputs = Enum.map(result["outputs"], &prepare_output/1)
+ outputs = result["outputs"] |> Enum.map(&prepare_output/1)
Map.replace(result, "outputs", outputs)
end
end
defp prepare_output(%{"type" => type, "value" => value} = output) do
- Map.replace(output, "value", ABIEncodedValueView.value_json(type, value))
+ Map.replace(output, "value", render_json(value, type))
end
defp prepare_output(output), do: output
# credo:disable-for-next-line
- def prepare_smart_contract(%Address{smart_contract: %SmartContract{}} = address) do
- minimal_proxy_template = Chain.get_minimal_proxy_template(address.hash, @api_true)
- twin = Chain.get_address_verified_twin_contract(address.hash, @api_true)
+ def prepare_smart_contract(%Address{smart_contract: %SmartContract{} = smart_contract} = address) do
+ minimal_proxy_template = EIP1167.get_implementation_address(address.hash, @api_true)
+ twin = SmartContract.get_address_verified_twin_contract(address.hash, @api_true)
metadata_for_verification = minimal_proxy_template || twin.verified_contract
smart_contract_verified = AddressView.smart_contract_verified?(address)
additional_sources_from_twin = twin.additional_sources
- fully_verified = Chain.smart_contract_fully_verified?(address.hash, @api_true)
+ fully_verified = SmartContract.verified_with_full_match?(address.hash, @api_true)
additional_sources =
if smart_contract_verified, do: address.smart_contract_additional_sources, else: additional_sources_from_twin
@@ -152,6 +156,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
"is_partially_verified" => address.smart_contract.partially_verified && smart_contract_verified,
"is_fully_verified" => fully_verified,
"is_verified_via_sourcify" => address.smart_contract.verified_via_sourcify && smart_contract_verified,
+ "is_verified_via_eth_bytecode_db" => address.smart_contract.verified_via_eth_bytecode_db,
"is_vyper_contract" => target_contract.is_vyper_contract,
"minimal_proxy_address_hash" =>
minimal_proxy_template && Address.checksum(metadata_for_verification.address_hash),
@@ -163,7 +168,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
visualize_sol2uml_enabled && !target_contract.is_vyper_contract && !is_nil(target_contract.abi),
"name" => target_contract && target_contract.name,
"compiler_version" => target_contract.compiler_version,
- "optimization_enabled" => if(target_contract.is_vyper_contract, do: nil, else: target_contract.optimization),
+ "optimization_enabled" => target_contract.optimization,
"optimization_runs" => target_contract.optimization_runs,
"evm_version" => target_contract.evm_version,
"verified_at" => target_contract.inserted_at,
@@ -177,7 +182,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
"decoded_constructor_args" =>
if(smart_contract_verified,
do: format_constructor_arguments(target_contract.abi, target_contract.constructor_arguments)
- )
+ ),
+ "language" => smart_contract_language(smart_contract)
}
|> Map.merge(bytecode_info(address))
end
@@ -227,7 +233,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
result =
constructor_arguments
- |> AddressContractView.decode_data(input_types)
+ |> decode_data(input_types)
|> Enum.zip(constructor_abi["inputs"])
|> Enum.map(fn {value, %{"type" => type} = input_arg} ->
[ABIEncodedValueView.value_json(type, value), input_arg]
@@ -252,9 +258,15 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
token = smart_contract.address.token
%{
- "address" => Helper.address_with_info(nil, smart_contract.address, smart_contract.address.hash),
+ "address" =>
+ Helper.address_with_info(
+ nil,
+ %Address{smart_contract.address | smart_contract: smart_contract},
+ smart_contract.address.hash,
+ false
+ ),
"compiler_version" => smart_contract.compiler_version,
- "optimization_enabled" => if(smart_contract.is_vyper_contract, do: nil, else: smart_contract.optimization),
+ "optimization_enabled" => smart_contract.optimization,
"tx_count" => smart_contract.address.transactions_count,
"language" => smart_contract_language(smart_contract),
"verified_at" => smart_contract.inserted_at,
@@ -277,4 +289,39 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
"solidity"
end
end
+
+ def render_json(%{"type" => type, "value" => value}) do
+ %{"type" => type, "value" => render_json(value, type)}
+ end
+
+ def render_json(value, type) when is_tuple(value) do
+ value
+ |> zip_tuple_values_with_types(type)
+ |> Enum.map(fn {type, value} ->
+ render_json(value, type)
+ end)
+ end
+
+ def render_json(value, type) when is_list(value) do
+ type =
+ if String.ends_with?(type, "[]") do
+ String.slice(type, 0..-3)
+ else
+ type
+ end
+
+ value |> Enum.map(&render_json(&1, type))
+ end
+
+ def render_json(value, type) when type in [:address, "address", "address payable"] do
+ SmartContractView.cast_address(value)
+ end
+
+ def render_json(value, type) when type in [:string, "string"] do
+ to_string(value)
+ end
+
+ def render_json(value, _type) do
+ value
+ end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex
index f1819125c3a9..616299fde941 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex
@@ -1,10 +1,30 @@
defmodule BlockScoutWeb.API.V2.TokenView do
+ use BlockScoutWeb, :view
+
alias BlockScoutWeb.API.V2.Helper
alias BlockScoutWeb.NFTHelper
- alias Explorer.Chain
+ alias Ecto.Association.NotLoaded
alias Explorer.Chain.Address
+ alias Explorer.Chain.Token.Instance
+
+ def render("token.json", %{token: nil, contract_address_hash: contract_address_hash}) do
+ %{
+ "address" => Address.checksum(contract_address_hash),
+ "symbol" => nil,
+ "name" => nil,
+ "decimals" => nil,
+ "type" => nil,
+ "holders" => nil,
+ "exchange_rate" => nil,
+ "total_supply" => nil,
+ "icon_url" => nil,
+ "circulating_market_cap" => nil
+ }
+ end
- @api_true [api?: true]
+ def render("token.json", %{token: nil}) do
+ nil
+ end
def render("token.json", %{token: token}) do
%{
@@ -13,10 +33,11 @@ defmodule BlockScoutWeb.API.V2.TokenView do
"name" => token.name,
"decimals" => token.decimals,
"type" => token.type,
- "holders" => token.holder_count && to_string(token.holder_count),
+ "holders" => prepare_holders_count(token.holder_count),
"exchange_rate" => exchange_rate(token),
"total_supply" => token.total_supply,
- "icon_url" => token.icon_url
+ "icon_url" => token.icon_url,
+ "circulating_market_cap" => token.circulating_market_cap
}
end
@@ -55,28 +76,42 @@ defmodule BlockScoutWeb.API.V2.TokenView do
def prepare_token_balance(token_balance, token) do
%{
- "address" => Helper.address_with_info(nil, token_balance.address, token_balance.address_hash),
+ "address" => Helper.address_with_info(nil, token_balance.address, token_balance.address_hash, false),
"value" => token_balance.value,
"token_id" => token_balance.token_id,
"token" => render("token.json", %{token: token})
}
end
+ @doc """
+ Internal json rendering function
+ """
def prepare_token_instance(instance, token) do
- is_unique =
- not (token.type == "ERC-1155") or
- Chain.token_id_1155_is_unique?(token.contract_address_hash, instance.token_id, @api_true)
-
%{
"id" => instance.token_id,
"metadata" => instance.metadata,
- "owner" =>
- if(is_unique, do: instance.owner && Helper.address_with_info(nil, instance.owner, instance.owner.hash)),
+ "owner" => token_instance_owner(instance.is_unique, instance),
"token" => render("token.json", %{token: token}),
"external_app_url" => NFTHelper.external_url(instance),
"animation_url" => instance.metadata && NFTHelper.retrieve_image(instance.metadata["animation_url"]),
"image_url" => instance.metadata && NFTHelper.get_media_src(instance.metadata, false),
- "is_unique" => is_unique
+ "is_unique" => instance.is_unique
}
end
+
+ defp token_instance_owner(false, _instance), do: nil
+ defp token_instance_owner(nil, _instance), do: nil
+
+ defp token_instance_owner(_is_unique, %Instance{owner: %NotLoaded{}} = instance),
+ do: Helper.address_with_info(nil, nil, instance.owner_address_hash, false)
+
+ defp token_instance_owner(_is_unique, %Instance{owner: nil} = instance),
+ do: Helper.address_with_info(nil, nil, instance.owner_address_hash, false)
+
+ defp token_instance_owner(_is_unique, instance),
+ do: instance.owner && Helper.address_with_info(nil, instance.owner, instance.owner.hash, false)
+
+ defp prepare_holders_count(nil), do: nil
+ defp prepare_holders_count(count) when count < 0, do: prepare_holders_count(0)
+ defp prepare_holders_count(count), do: to_string(count)
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex
index ee27b02290d5..a3ac1335ea45 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex
@@ -7,17 +7,20 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
alias BlockScoutWeb.Tokens.Helper, as: TokensHelper
alias BlockScoutWeb.TransactionStateView
alias Ecto.Association.NotLoaded
- alias Explorer.ExchangeRates.Token, as: TokenRate
alias Explorer.{Chain, Market}
- alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Token, Transaction, Wei}
+ alias Explorer.Chain.{Address, Block, Hash, InternalTransaction, Log, Token, Transaction, Wei}
alias Explorer.Chain.Block.Reward
+ alias Explorer.Chain.PolygonEdge.Reader
alias Explorer.Chain.Transaction.StateChange
alias Explorer.Counters.AverageBlockTime
alias Timex.Duration
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
+ import Explorer.Chain.Transaction, only: [maybe_prepare_stability_fees: 1, bytes_to_address_hash: 1]
+ import Explorer.Helper, only: [decode_data: 2]
@api_true [api?: true]
+ @suave_bid_event "0x83481d5b04dea534715acad673a8177a46fc93882760f36bdc16ccac439d504e"
def render("message.json", assigns) do
ApiView.render("message.json", assigns)
@@ -29,8 +32,17 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
conn: conn,
watchlist_names: watchlist_names
}) do
+ block_height = Chain.block_height(@api_true)
+ {decoded_transactions, _, _} = decode_transactions(transactions, true)
+
%{
- "items" => Enum.map(transactions, &prepare_transaction(&1, conn, false, watchlist_names)),
+ "items" =>
+ transactions
+ |> maybe_prepare_stability_fees()
+ |> Enum.zip(decoded_transactions)
+ |> Enum.map(fn {tx, decoded_input} ->
+ prepare_transaction(tx, conn, false, block_height, watchlist_names, decoded_input)
+ end),
"next_page_params" => next_page_params
}
end
@@ -40,19 +52,51 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
conn: conn,
watchlist_names: watchlist_names
}) do
- Enum.map(transactions, &prepare_transaction(&1, conn, false, watchlist_names))
+ block_height = Chain.block_height(@api_true)
+ {decoded_transactions, _, _} = decode_transactions(transactions, true)
+
+ transactions
+ |> maybe_prepare_stability_fees()
+ |> Enum.zip(decoded_transactions)
+ |> Enum.map(fn {tx, decoded_input} ->
+ prepare_transaction(tx, conn, false, block_height, watchlist_names, decoded_input)
+ end)
end
def render("transactions.json", %{transactions: transactions, next_page_params: next_page_params, conn: conn}) do
- %{"items" => Enum.map(transactions, &prepare_transaction(&1, conn, false)), "next_page_params" => next_page_params}
+ block_height = Chain.block_height(@api_true)
+ {decoded_transactions, _, _} = decode_transactions(transactions, true)
+
+ %{
+ "items" =>
+ transactions
+ |> maybe_prepare_stability_fees()
+ |> Enum.zip(decoded_transactions)
+ |> Enum.map(fn {tx, decoded_input} -> prepare_transaction(tx, conn, false, block_height, decoded_input) end),
+ "next_page_params" => next_page_params
+ }
+ end
+
+ def render("transactions.json", %{transactions: transactions, items: true, conn: conn}) do
+ %{
+ "items" => render("transactions.json", %{transactions: transactions, conn: conn})
+ }
end
def render("transactions.json", %{transactions: transactions, conn: conn}) do
- Enum.map(transactions, &prepare_transaction(&1, conn, false))
+ block_height = Chain.block_height(@api_true)
+ {decoded_transactions, _, _} = decode_transactions(transactions, true)
+
+ transactions
+ |> maybe_prepare_stability_fees()
+ |> Enum.zip(decoded_transactions)
+ |> Enum.map(fn {tx, decoded_input} -> prepare_transaction(tx, conn, false, block_height, decoded_input) end)
end
def render("transaction.json", %{transaction: transaction, conn: conn}) do
- prepare_transaction(transaction, conn, true)
+ block_height = Chain.block_height(@api_true)
+ {[decoded_input], _, _} = decode_transactions([transaction], false)
+ prepare_transaction(transaction |> maybe_prepare_stability_fees(), conn, true, block_height, decoded_input)
end
def render("raw_trace.json", %{internal_transactions: internal_transactions}) do
@@ -72,15 +116,28 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
end
def render("token_transfers.json", %{token_transfers: token_transfers, next_page_params: next_page_params, conn: conn}) do
- %{"items" => Enum.map(token_transfers, &prepare_token_transfer(&1, conn)), "next_page_params" => next_page_params}
+ {decoded_transactions, _, _} = decode_transactions(Enum.map(token_transfers, fn tt -> tt.transaction end), true)
+
+ %{
+ "items" =>
+ token_transfers
+ |> Enum.zip(decoded_transactions)
+ |> Enum.map(fn {tt, decoded_input} -> prepare_token_transfer(tt, conn, decoded_input) end),
+ "next_page_params" => next_page_params
+ }
end
def render("token_transfers.json", %{token_transfers: token_transfers, conn: conn}) do
- Enum.map(token_transfers, &prepare_token_transfer(&1, conn))
+ {decoded_transactions, _, _} = decode_transactions(Enum.map(token_transfers, fn tt -> tt.transaction end), true)
+
+ token_transfers
+ |> Enum.zip(decoded_transactions)
+ |> Enum.map(fn {tt, decoded_input} -> prepare_token_transfer(tt, conn, decoded_input) end)
end
def render("token_transfer.json", %{token_transfer: token_transfer, conn: conn}) do
- prepare_token_transfer(token_transfer, conn)
+ {[decoded_transaction], _, _} = decode_transactions([token_transfer.transaction], true)
+ prepare_token_transfer(token_transfer, conn, decoded_transaction)
end
def render("transaction_actions.json", %{actions: actions}) do
@@ -99,28 +156,74 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
end
def render("logs.json", %{logs: logs, next_page_params: next_page_params, tx_hash: tx_hash}) do
- %{"items" => Enum.map(logs, fn log -> prepare_log(log, tx_hash, false) end), "next_page_params" => next_page_params}
+ decoded_logs = decode_logs(logs, false)
+
+ %{
+ "items" =>
+ logs |> Enum.zip(decoded_logs) |> Enum.map(fn {log, decoded_log} -> prepare_log(log, tx_hash, decoded_log) end),
+ "next_page_params" => next_page_params
+ }
end
def render("logs.json", %{logs: logs, next_page_params: next_page_params}) do
+ decoded_logs = decode_logs(logs, false)
+
%{
- "items" => Enum.map(logs, fn log -> prepare_log(log, log.transaction, true) end),
+ "items" =>
+ logs
+ |> Enum.zip(decoded_logs)
+ |> Enum.map(fn {log, decoded_log} -> prepare_log(log, log.transaction, decoded_log) end),
"next_page_params" => next_page_params
}
end
- def render("state_changes.json", %{state_changes: state_changes}) do
- Enum.map(state_changes, &prepare_state_change(&1))
+ def render("state_changes.json", %{state_changes: state_changes, next_page_params: next_page_params}) do
+ %{
+ "items" => Enum.map(state_changes, &prepare_state_change(&1)),
+ "next_page_params" => next_page_params
+ }
+ end
+
+ @doc """
+ Decodes list of logs
+ """
+ @spec decode_logs([Log.t()], boolean) :: [tuple]
+ def decode_logs(logs, skip_sig_provider?) do
+ {result, _, _} =
+ Enum.reduce(logs, {[], %{}, %{}}, fn log, {results, contracts_acc, events_acc} ->
+ {result, contracts_acc, events_acc} =
+ Log.decode(
+ log,
+ %Transaction{hash: log.transaction_hash},
+ @api_true,
+ skip_sig_provider?,
+ contracts_acc,
+ events_acc
+ )
+
+ {[format_decoded_log_input(result) | results], contracts_acc, events_acc}
+ end)
+
+ Enum.reverse(result)
end
- def prepare_token_transfer(token_transfer, _conn) do
- decoded_input =
- token_transfer.transaction |> Transaction.decoded_input_data(true, @api_true) |> format_decoded_input()
+ def decode_transactions(transactions, skip_sig_provider?) do
+ {results, abi_acc, methods_acc} =
+ Enum.reduce(transactions, {[], %{}, %{}}, fn transaction, {results, abi_acc, methods_acc} ->
+ {result, abi_acc, methods_acc} =
+ Transaction.decoded_input_data(transaction, skip_sig_provider?, @api_true, abi_acc, methods_acc)
+ {[format_decoded_input(result) | results], abi_acc, methods_acc}
+ end)
+
+ {Enum.reverse(results), abi_acc, methods_acc}
+ end
+
+ def prepare_token_transfer(token_transfer, _conn, decoded_input) do
%{
"tx_hash" => token_transfer.transaction_hash,
- "from" => Helper.address_with_info(nil, token_transfer.from_address, token_transfer.from_address_hash),
- "to" => Helper.address_with_info(nil, token_transfer.to_address, token_transfer.to_address_hash),
+ "from" => Helper.address_with_info(nil, token_transfer.from_address, token_transfer.from_address_hash, false),
+ "to" => Helper.address_with_info(nil, token_transfer.to_address, token_transfer.to_address_hash, false),
"total" => prepare_token_transfer_total(token_transfer),
"token" => TokenView.render("token.json", %{token: token_transfer.token}),
"type" => Chain.get_token_transfer_type(token_transfer),
@@ -152,9 +255,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
%{"token_id" => List.first(token_transfer.token_ids), "value" => value, "decimals" => decimals}
{:ok, :erc1155_instance, values, token_ids, decimals} ->
- Enum.map(Enum.zip(values, token_ids), fn {value, token_id} ->
- %{"value" => value, "token_id" => token_id, "decimals" => decimals}
- end)
+ %{"token_id" => List.first(token_ids), "value" => List.first(values), "decimals" => decimals}
{:ok, value, decimals} ->
%{"value" => value, "decimals" => decimals}
@@ -171,28 +272,30 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
"type" => internal_transaction.call_type || internal_transaction.type,
"transaction_hash" => internal_transaction.transaction_hash,
"from" =>
- Helper.address_with_info(nil, internal_transaction.from_address, internal_transaction.from_address_hash),
- "to" => Helper.address_with_info(nil, internal_transaction.to_address, internal_transaction.to_address_hash),
+ Helper.address_with_info(nil, internal_transaction.from_address, internal_transaction.from_address_hash, false),
+ "to" =>
+ Helper.address_with_info(nil, internal_transaction.to_address, internal_transaction.to_address_hash, false),
"created_contract" =>
Helper.address_with_info(
nil,
internal_transaction.created_contract_address,
- internal_transaction.created_contract_address_hash
+ internal_transaction.created_contract_address_hash,
+ false
),
"value" => internal_transaction.value,
"block" => internal_transaction.block_number,
- "timestamp" => internal_transaction.transaction.block.timestamp,
+ "timestamp" => internal_transaction.block.timestamp,
"index" => internal_transaction.index,
"gas_limit" => internal_transaction.gas
}
end
- def prepare_log(log, transaction_or_hash, skip_sig_provider?) do
- decoded = decode_log(log, transaction_or_hash, skip_sig_provider?)
+ def prepare_log(log, transaction_or_hash, decoded_log, tags_for_address_needed? \\ false) do
+ decoded = process_decoded_log(decoded_log)
%{
"tx_hash" => get_tx_hash(transaction_or_hash),
- "address" => Helper.address_with_info(log.address, log.address_hash),
+ "address" => Helper.address_with_info(nil, log.address, log.address_hash, tags_for_address_needed?),
"topics" => [
log.first_topic,
log.second_topic,
@@ -202,18 +305,22 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
"data" => log.data,
"index" => log.index,
"decoded" => decoded,
- "smart_contract" => smart_contract_info(transaction_or_hash)
+ "smart_contract" => smart_contract_info(transaction_or_hash),
+ "block_number" => log.block_number,
+ "block_hash" => log.block_hash
}
end
defp get_tx_hash(%Transaction{} = tx), do: to_string(tx.hash)
defp get_tx_hash(hash), do: to_string(hash)
- defp smart_contract_info(%Transaction{} = tx), do: Helper.address_with_info(tx.to_address, tx.to_address_hash)
+ defp smart_contract_info(%Transaction{} = tx),
+ do: Helper.address_with_info(nil, tx.to_address, tx.to_address_hash, false)
+
defp smart_contract_info(_), do: nil
- defp decode_log(log, %Transaction{} = tx, skip_sig_provider?) do
- case log |> Log.decode(tx, @api_true, skip_sig_provider?) |> format_decoded_log_input() do
+ defp process_decoded_log(decoded_log) do
+ case decoded_log do
{:ok, method_id, text, mapping} ->
render(__MODULE__, "decoded_log_input.json", method_id: method_id, text: text, mapping: mapping)
@@ -222,46 +329,59 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
end
end
- defp decode_log(log, transaction_hash, skip_sig_provider?),
- do: decode_log(log, %Transaction{hash: transaction_hash}, skip_sig_provider?)
+ defp prepare_transaction(tx, conn, single_tx?, block_height, watchlist_names \\ nil, decoded_input)
- defp prepare_transaction({%Reward{} = emission_reward, %Reward{} = validator_reward}, conn, single_tx?) do
+ defp prepare_transaction(
+ {%Reward{} = emission_reward, %Reward{} = validator_reward},
+ conn,
+ single_tx?,
+ _block_height,
+ _watchlist_names,
+ _decoded_input
+ ) do
%{
"emission_reward" => emission_reward.reward,
"block_hash" => validator_reward.block_hash,
- "from" => Helper.address_with_info(single_tx? && conn, emission_reward.address, emission_reward.address_hash),
- "to" => Helper.address_with_info(single_tx? && conn, validator_reward.address, validator_reward.address_hash),
+ "from" =>
+ Helper.address_with_info(single_tx? && conn, emission_reward.address, emission_reward.address_hash, single_tx?),
+ "to" =>
+ Helper.address_with_info(
+ single_tx? && conn,
+ validator_reward.address,
+ validator_reward.address_hash,
+ single_tx?
+ ),
"types" => [:reward]
}
end
- defp prepare_transaction(%Transaction{} = transaction, conn, single_tx?, watchlist_names \\ nil) do
+ defp prepare_transaction(%Transaction{} = transaction, conn, single_tx?, block_height, watchlist_names, decoded_input) do
base_fee_per_gas = transaction.block && transaction.block.base_fee_per_gas
max_priority_fee_per_gas = transaction.max_priority_fee_per_gas
max_fee_per_gas = transaction.max_fee_per_gas
priority_fee_per_gas = priority_fee_per_gas(max_priority_fee_per_gas, base_fee_per_gas, max_fee_per_gas)
- burned_fee = burned_fee(transaction, max_fee_per_gas, base_fee_per_gas)
+ burnt_fees = burnt_fees(transaction, max_fee_per_gas, base_fee_per_gas)
status = transaction |> Chain.transaction_to_status() |> format_status()
revert_reason = revert_reason(status, transaction)
- decoded_input = transaction |> Transaction.decoded_input_data(!single_tx?, @api_true) |> format_decoded_input()
decoded_input_data = decoded_input(decoded_input)
- %{
+ result = %{
"hash" => transaction.hash,
"result" => status,
"status" => transaction.status,
"block" => transaction.block_number,
- "timestamp" => block_timestamp(transaction.block),
+ "timestamp" => block_timestamp(transaction),
"from" =>
Helper.address_with_info(
single_tx? && conn,
transaction.from_address,
transaction.from_address_hash,
+ single_tx?,
watchlist_names
),
"to" =>
@@ -269,6 +389,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
single_tx? && conn,
transaction.to_address,
transaction.to_address_hash,
+ single_tx?,
watchlist_names
),
"created_contract" =>
@@ -276,10 +397,10 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
single_tx? && conn,
transaction.created_contract_address,
transaction.created_contract_address_hash,
+ single_tx?,
watchlist_names
),
- "confirmations" =>
- transaction.block |> Chain.confirmations(block_height: Chain.block_height(@api_true)) |> format_confirmations(),
+ "confirmations" => transaction.block |> Chain.confirmations(block_height: block_height) |> format_confirmations(),
"confirmation_duration" => processing_time_duration(transaction),
"value" => transaction.value,
"fee" => transaction |> Chain.fee(:wei) |> format_fee(),
@@ -291,7 +412,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
"max_priority_fee_per_gas" => transaction.max_priority_fee_per_gas,
"base_fee_per_gas" => base_fee_per_gas,
"priority_fee" => priority_fee_per_gas && Wei.mult(priority_fee_per_gas, transaction.gas_used),
- "tx_burnt_fee" => burned_fee,
+ "tx_burnt_fee" => burnt_fees,
"nonce" => transaction.nonce,
"position" => transaction.index,
"revert_reason" => revert_reason,
@@ -300,12 +421,158 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
"token_transfers" => token_transfers(transaction.token_transfers, conn, single_tx?),
"token_transfers_overflow" => token_transfers_overflow(transaction.token_transfers, single_tx?),
"actions" => transaction_actions(transaction.transaction_actions),
- "exchange_rate" => (Market.get_exchange_rate(Explorer.coin()) || TokenRate.null()).usd_value,
+ "exchange_rate" => Market.get_coin_exchange_rate().usd_value,
"method" => method_name(transaction, decoded_input),
"tx_types" => tx_types(transaction),
"tx_tag" => GetTransactionTags.get_transaction_tags(transaction.hash, current_user(single_tx? && conn)),
"has_error_in_internal_txs" => transaction.has_error_in_internal_txs
}
+
+ result
+ |> chain_type_fields(transaction, single_tx?, conn, watchlist_names)
+ |> maybe_put_stability_fee(transaction)
+ end
+
+ defp chain_type_fields(result, transaction, single_tx?, conn, watchlist_names) do
+ case single_tx? && Application.get_env(:explorer, :chain_type) do
+ "polygon_edge" ->
+ result
+ |> Map.put("polygon_edge_deposit", polygon_edge_deposit(transaction.hash, conn))
+ |> Map.put("polygon_edge_withdrawal", polygon_edge_withdrawal(transaction.hash, conn))
+
+ "polygon_zkevm" ->
+ extended_result =
+ result
+ |> add_optional_transaction_field(transaction, "zkevm_batch_number", :zkevm_batch, :number)
+ |> add_optional_transaction_field(transaction, "zkevm_sequence_hash", :zkevm_sequence_transaction, :hash)
+ |> add_optional_transaction_field(transaction, "zkevm_verify_hash", :zkevm_verify_transaction, :hash)
+
+ Map.put(extended_result, "zkevm_status", zkevm_status(extended_result))
+
+ "suave" ->
+ suave_fields(transaction, result, single_tx?, conn, watchlist_names)
+
+ _ ->
+ result
+ end
+ end
+
+ defp add_optional_transaction_field(result, transaction, field_name, assoc_name, assoc_field) do
+ case Map.get(transaction, assoc_name) do
+ nil -> result
+ %Ecto.Association.NotLoaded{} -> result
+ item -> Map.put(result, field_name, Map.get(item, assoc_field))
+ end
+ end
+
+ defp zkevm_status(result_map) do
+ if is_nil(Map.get(result_map, "zkevm_sequence_hash")) do
+ "Confirmed by Sequencer"
+ else
+ "L1 Confirmed"
+ end
+ end
+
+ defp suave_fields(transaction, result, single_tx?, conn, watchlist_names) do
+ if is_nil(transaction.execution_node_hash) do
+ result
+ else
+ {[wrapped_decoded_input], _, _} =
+ decode_transactions(
+ [
+ %Transaction{
+ to_address: transaction.wrapped_to_address,
+ input: transaction.wrapped_input,
+ hash: transaction.wrapped_hash
+ }
+ ],
+ false
+ )
+
+ result
+ |> Map.put("allowed_peekers", suave_parse_allowed_peekers(transaction.logs))
+ |> Map.put(
+ "execution_node",
+ Helper.address_with_info(
+ single_tx? && conn,
+ transaction.execution_node,
+ transaction.execution_node_hash,
+ single_tx?,
+ watchlist_names
+ )
+ )
+ |> Map.put("wrapped", %{
+ "type" => transaction.wrapped_type,
+ "nonce" => transaction.wrapped_nonce,
+ "to" =>
+ Helper.address_with_info(
+ single_tx? && conn,
+ transaction.wrapped_to_address,
+ transaction.wrapped_to_address_hash,
+ single_tx?,
+ watchlist_names
+ ),
+ "gas_limit" => transaction.wrapped_gas,
+ "gas_price" => transaction.wrapped_gas_price,
+ "fee" =>
+ format_fee(
+ Chain.fee(
+ %Transaction{gas: transaction.wrapped_gas, gas_price: transaction.wrapped_gas_price, gas_used: nil},
+ :wei
+ )
+ ),
+ "max_priority_fee_per_gas" => transaction.wrapped_max_priority_fee_per_gas,
+ "max_fee_per_gas" => transaction.wrapped_max_fee_per_gas,
+ "value" => transaction.wrapped_value,
+ "hash" => transaction.wrapped_hash,
+ "method" =>
+ method_name(
+ %Transaction{to_address: transaction.wrapped_to_address, input: transaction.wrapped_input},
+ wrapped_decoded_input
+ ),
+ "decoded_input" => decoded_input(wrapped_decoded_input),
+ "raw_input" => transaction.wrapped_input
+ })
+ end
+ end
+
+ defp suave_parse_allowed_peekers(logs) do
+ suave_bid_contracts =
+ Application.get_all_env(:explorer)[Transaction][:suave_bid_contracts]
+ |> String.split(",")
+ |> Enum.map(fn sbc -> String.downcase(String.trim(sbc)) end)
+
+ bid_event =
+ Enum.find(logs, fn log ->
+ sanitize_log_first_topic(log.first_topic) == @suave_bid_event &&
+ Enum.member?(suave_bid_contracts, String.downcase(Hash.to_string(log.address_hash)))
+ end)
+
+ if is_nil(bid_event) do
+ []
+ else
+ [_bid_id, _decryption_condition, allowed_peekers] =
+ decode_data(bid_event.data, [{:bytes, 16}, {:uint, 64}, {:array, :address}])
+
+ Enum.map(allowed_peekers, fn peeker ->
+ "0x" <> Base.encode16(peeker, case: :lower)
+ end)
+ end
+ end
+
+ defp sanitize_log_first_topic(first_topic) do
+ if is_nil(first_topic) do
+ ""
+ else
+ sanitized =
+ if is_binary(first_topic) do
+ first_topic
+ else
+ Hash.to_string(first_topic)
+ end
+
+ String.downcase(sanitized)
+ end
end
def token_transfers(_, _conn, false), do: nil
@@ -324,9 +591,12 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
def token_transfers_overflow(token_transfers, _),
do: Enum.count(token_transfers) > Chain.get_token_transfers_per_transaction_preview_count()
- defp transaction_actions(%NotLoaded{}), do: []
+ def transaction_actions(%NotLoaded{}), do: []
- defp transaction_actions(actions) do
+ @doc """
+ Renders transaction actions
+ """
+ def transaction_actions(actions) do
render("transaction_actions.json", %{actions: actions})
end
@@ -339,7 +609,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
end)
end
- defp burned_fee(transaction, max_fee_per_gas, base_fee_per_gas) do
+ defp burnt_fees(transaction, max_fee_per_gas, base_fee_per_gas) do
if !is_nil(max_fee_per_gas) and !is_nil(transaction.gas_used) and !is_nil(base_fee_per_gas) do
if Decimal.compare(max_fee_per_gas.value, 0) == :eq do
%Wei{value: Decimal.new(0)}
@@ -368,7 +638,11 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
end
end
- defp decoded_input(decoded_input) do
+ @doc """
+ Prepares decoded tx info
+ """
+ @spec decoded_input(any()) :: map() | nil
+ def decoded_input(decoded_input) do
case decoded_input do
{:ok, method_id, text, mapping} ->
render(__MODULE__, "decoded_input.json", method_id: method_id, text: text, mapping: mapping, error?: false)
@@ -393,13 +667,13 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
defp format_status({:error, reason}), do: reason
defp format_status(status), do: status
- defp format_decoded_input({:error, _, []}), do: nil
- defp format_decoded_input({:error, _, candidates}), do: Enum.at(candidates, 0)
- defp format_decoded_input({:ok, _identifier, _text, _mapping} = decoded), do: decoded
- defp format_decoded_input(_), do: nil
+ @spec format_decoded_input(any()) :: nil | map() | tuple()
+ def format_decoded_input({:error, _, []}), do: nil
+ def format_decoded_input({:error, _, candidates}), do: Enum.at(candidates, 0)
+ def format_decoded_input({:ok, _identifier, _text, _mapping} = decoded), do: decoded
+ def format_decoded_input(_), do: nil
defp format_decoded_log_input({:error, :could_not_decode}), do: nil
- defp format_decoded_log_input({:error, :no_matching_function}), do: nil
defp format_decoded_log_input({:ok, _method_id, _text, _mapping} = decoded), do: decoded
defp format_decoded_log_input({:error, _, candidates}), do: Enum.at(candidates, 0)
@@ -447,33 +721,54 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
|> Timex.diff(right, :milliseconds)
end
- defp method_name(_, _, skip_sc_check? \\ false)
+ @doc """
+ Return method name used in tx
+ """
+ @spec method_name(Transaction.t(), any(), boolean()) :: binary() | nil
+ def method_name(_, _, skip_sc_check? \\ false)
- defp method_name(_, {:ok, _method_id, text, _mapping}, _) do
+ def method_name(_, {:ok, _method_id, text, _mapping}, _) do
Transaction.parse_method_name(text, false)
end
- defp method_name(
- %Transaction{to_address: to_address, input: %{bytes: <>}},
- _,
- skip_sc_check?
- ) do
- if skip_sc_check? || Helper.is_smart_contract(to_address) do
+ def method_name(
+ %Transaction{to_address: to_address, input: %{bytes: <>}},
+ _,
+ skip_sc_check?
+ ) do
+ if skip_sc_check? || Address.is_smart_contract(to_address) do
"0x" <> Base.encode16(method_id, case: :lower)
else
nil
end
end
- defp method_name(_, _, _) do
+ def method_name(_, _, _) do
nil
end
- defp tx_types(tx, types \\ [], stage \\ :token_transfer)
-
- defp tx_types(%Transaction{token_transfers: token_transfers} = tx, types, :token_transfer) do
+ @doc """
+ Returns array of token types for tx.
+ """
+ @spec tx_types(
+ Explorer.Chain.Transaction.t(),
+ [tx_type],
+ tx_type
+ ) :: [tx_type]
+ when tx_type:
+ :coin_transfer
+ | :contract_call
+ | :contract_creation
+ | :rootstock_bridge
+ | :rootstock_remasc
+ | :token_creation
+ | :token_transfer
+ def tx_types(tx, types \\ [], stage \\ :token_transfer)
+
+ def tx_types(%Transaction{token_transfers: token_transfers} = tx, types, :token_transfer) do
types =
- if !is_nil(token_transfers) && token_transfers != [] && !match?(%NotLoaded{}, token_transfers) do
+ if (!is_nil(token_transfers) && token_transfers != [] && !match?(%NotLoaded{}, token_transfers)) ||
+ tx.has_token_transfers do
[:token_transfer | types]
else
types
@@ -482,7 +777,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
tx_types(tx, types, :token_creation)
end
- defp tx_types(%Transaction{created_contract_address: created_contract_address} = tx, types, :token_creation) do
+ def tx_types(%Transaction{created_contract_address: created_contract_address} = tx, types, :token_creation) do
types =
if match?(%Address{}, created_contract_address) && match?(%Token{}, created_contract_address.token) do
[:token_creation | types]
@@ -493,11 +788,11 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
tx_types(tx, types, :contract_creation)
end
- defp tx_types(
- %Transaction{to_address_hash: to_address_hash} = tx,
- types,
- :contract_creation
- ) do
+ def tx_types(
+ %Transaction{to_address_hash: to_address_hash} = tx,
+ types,
+ :contract_creation
+ ) do
types =
if is_nil(to_address_hash) do
[:contract_creation | types]
@@ -508,9 +803,9 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
tx_types(tx, types, :contract_call)
end
- defp tx_types(%Transaction{to_address: to_address} = tx, types, :contract_call) do
+ def tx_types(%Transaction{to_address: to_address} = tx, types, :contract_call) do
types =
- if Helper.is_smart_contract(to_address) do
+ if Address.is_smart_contract(to_address) do
[:contract_call | types]
else
types
@@ -519,14 +814,37 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
tx_types(tx, types, :coin_transfer)
end
- defp tx_types(%Transaction{value: value}, types, :coin_transfer) do
- if Decimal.compare(value.value, 0) == :gt do
- [:coin_transfer | types]
+ def tx_types(%Transaction{value: value} = tx, types, :coin_transfer) do
+ types =
+ if Decimal.compare(value.value, 0) == :gt do
+ [:coin_transfer | types]
+ else
+ types
+ end
+
+ tx_types(tx, types, :rootstock_remasc)
+ end
+
+ def tx_types(tx, types, :rootstock_remasc) do
+ types =
+ if Transaction.is_rootstock_remasc_transaction(tx) do
+ [:rootstock_remasc | types]
+ else
+ types
+ end
+
+ tx_types(tx, types, :rootstock_bridge)
+ end
+
+ def tx_types(tx, types, :rootstock_bridge) do
+ if Transaction.is_rootstock_bridge_transaction(tx) do
+ [:rootstock_bridge | types]
else
types
end
end
+ defp block_timestamp(%Transaction{block_timestamp: block_ts}) when not is_nil(block_ts), do: block_ts
defp block_timestamp(%Transaction{block: %Block{} = block}), do: block.timestamp
defp block_timestamp(%Block{} = block), do: block.timestamp
defp block_timestamp(_), do: nil
@@ -541,10 +859,11 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
%{
"address" =>
- Helper.address_with_info(nil, state_change.address, state_change.address && state_change.address.hash),
+ Helper.address_with_info(nil, state_change.address, state_change.address && state_change.address.hash, false),
"is_miner" => state_change.miner?,
"type" => type,
- "token" => if(type == "token", do: TokenView.render("token.json", %{token: coin_or_transfer.token}))
+ "token" => if(type == "token", do: TokenView.render("token.json", %{token: coin_or_transfer.token})),
+ "token_id" => state_change.token_id
}
|> append_balances(state_change.balance_before, state_change.balance_after)
|> append_balance_change(state_change, coin_or_transfer)
@@ -569,7 +888,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
defp append_balance_change(map, state_change, coin_or_transfer) do
change =
- if is_list(state_change.coin_or_token_transfers) and coin_or_transfer.token.type != "ERC-20" do
+ if is_list(state_change.coin_or_token_transfers) and coin_or_transfer.token.type == "ERC-721" do
for {direction, token_transfer} <- state_change.coin_or_token_transfers do
%{"total" => prepare_token_transfer_total(token_transfer), "direction" => direction}
end
@@ -579,4 +898,72 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
Map.merge(map, %{"change" => change})
end
+
+ defp polygon_edge_deposit(transaction_hash, conn) do
+ transaction_hash
+ |> Reader.deposit_by_transaction_hash()
+ |> polygon_edge_deposit_or_withdrawal(conn)
+ end
+
+ defp polygon_edge_withdrawal(transaction_hash, conn) do
+ transaction_hash
+ |> Reader.withdrawal_by_transaction_hash()
+ |> polygon_edge_deposit_or_withdrawal(conn)
+ end
+
+ defp polygon_edge_deposit_or_withdrawal(item, conn) do
+ if not is_nil(item) do
+ {from_address, from_address_hash} = hash_to_address_and_hash(item.from)
+ {to_address, to_address_hash} = hash_to_address_and_hash(item.to)
+
+ item
+ |> Map.put(:from, Helper.address_with_info(conn, from_address, from_address_hash, item.from))
+ |> Map.put(:to, Helper.address_with_info(conn, to_address, to_address_hash, item.to))
+ end
+ end
+
+ defp hash_to_address_and_hash(hash) do
+ with false <- is_nil(hash),
+ {:ok, address} <-
+ Chain.hash_to_address(
+ hash,
+ [necessity_by_association: %{:names => :optional, :smart_contract => :optional}, api?: true],
+ false
+ ) do
+ {address, address.hash}
+ else
+ _ -> {nil, nil}
+ end
+ end
+
+ defp maybe_put_stability_fee(body, transaction) do
+ with "stability" <- Application.get_env(:explorer, :chain_type),
+ [
+ {"token", "address", false, token_address_hash},
+ {"totalFee", "uint256", false, total_fee},
+ {"validator", "address", false, validator_address_hash},
+ {"validatorFee", "uint256", false, validator_fee},
+ {"dapp", "address", false, dapp_address_hash},
+ {"dappFee", "uint256", false, dapp_fee}
+ ] <- transaction.transaction_fee_log do
+ stability_fee = %{
+ "token" =>
+ TokenView.render("token.json", %{
+ token: transaction.transaction_fee_token,
+ contract_address_hash: bytes_to_address_hash(token_address_hash)
+ }),
+ "validator_address" => Helper.address_with_info(nil, nil, bytes_to_address_hash(validator_address_hash), false),
+ "dapp_address" => Helper.address_with_info(nil, nil, bytes_to_address_hash(dapp_address_hash), false),
+ "total_fee" => to_string(total_fee),
+ "dapp_fee" => to_string(dapp_fee),
+ "validator_fee" => to_string(validator_fee)
+ }
+
+ body
+ |> Map.put("stability_fee", stability_fee)
+ else
+ _ ->
+ body
+ end
+ end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/withdrawal_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/withdrawal_view.ex
index 299d4629721e..252a36f784e8 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/withdrawal_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/withdrawal_view.ex
@@ -13,7 +13,7 @@ defmodule BlockScoutWeb.API.V2.WithdrawalView do
%{
"index" => withdrawal.index,
"validator_index" => withdrawal.validator_index,
- "receiver" => Helper.address_with_info(withdrawal.address, withdrawal.address_hash),
+ "receiver" => Helper.address_with_info(nil, withdrawal.address, withdrawal.address_hash, false),
"amount" => withdrawal.amount
}
end
@@ -33,7 +33,7 @@ defmodule BlockScoutWeb.API.V2.WithdrawalView do
"index" => withdrawal.index,
"validator_index" => withdrawal.validator_index,
"block_number" => withdrawal.block.number,
- "receiver" => Helper.address_with_info(withdrawal.address, withdrawal.address_hash),
+ "receiver" => Helper.address_with_info(nil, withdrawal.address, withdrawal.address_hash, false),
"amount" => withdrawal.amount,
"timestamp" => withdrawal.block.timestamp
}
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/zkevm_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/zkevm_view.ex
new file mode 100644
index 000000000000..a4b0eb2b0c50
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/zkevm_view.ex
@@ -0,0 +1,104 @@
+defmodule BlockScoutWeb.API.V2.ZkevmView do
+ use BlockScoutWeb, :view
+
+ @doc """
+ Function to render GET requests to `/api/v2/zkevm/batches/:batch_number` endpoint.
+ """
+ @spec render(binary(), map()) :: map() | non_neg_integer()
+ def render("zkevm_batch.json", %{batch: batch}) do
+ sequence_tx_hash =
+ if Map.has_key?(batch, :sequence_transaction) and not is_nil(batch.sequence_transaction) do
+ batch.sequence_transaction.hash
+ end
+
+ verify_tx_hash =
+ if Map.has_key?(batch, :verify_transaction) and not is_nil(batch.verify_transaction) do
+ batch.verify_transaction.hash
+ end
+
+ l2_transactions =
+ if Map.has_key?(batch, :l2_transactions) do
+ Enum.map(batch.l2_transactions, fn tx -> tx.hash end)
+ end
+
+ %{
+ "number" => batch.number,
+ "status" => batch_status(batch),
+ "timestamp" => batch.timestamp,
+ "transactions" => l2_transactions,
+ "global_exit_root" => batch.global_exit_root,
+ "acc_input_hash" => batch.acc_input_hash,
+ "sequence_tx_hash" => sequence_tx_hash,
+ "verify_tx_hash" => verify_tx_hash,
+ "state_root" => batch.state_root
+ }
+ end
+
+ @doc """
+ Function to render GET requests to `/api/v2/zkevm/batches` endpoint.
+ """
+ def render("zkevm_batches.json", %{
+ batches: batches,
+ next_page_params: next_page_params
+ }) do
+ %{
+ items: render_zkevm_batches(batches),
+ next_page_params: next_page_params
+ }
+ end
+
+ @doc """
+ Function to render GET requests to `/api/v2/main-page/zkevm/batches/confirmed` endpoint.
+ """
+ def render("zkevm_batches.json", %{batches: batches}) do
+ %{items: render_zkevm_batches(batches)}
+ end
+
+ @doc """
+ Function to render GET requests to `/api/v2/zkevm/batches/count` endpoint.
+ """
+ def render("zkevm_batches_count.json", %{count: count}) do
+ count
+ end
+
+ @doc """
+ Function to render GET requests to `/api/v2/main-page/zkevm/batches/latest-number` endpoint.
+ """
+ def render("zkevm_batch_latest_number.json", %{number: number}) do
+ number
+ end
+
+ defp batch_status(batch) do
+ sequence_id = Map.get(batch, :sequence_id)
+ verify_id = Map.get(batch, :verify_id)
+
+ cond do
+ is_nil(sequence_id) && is_nil(verify_id) -> "Unfinalized"
+ !is_nil(sequence_id) && is_nil(verify_id) -> "L1 Sequence Confirmed"
+ !is_nil(verify_id) -> "Finalized"
+ end
+ end
+
+ defp render_zkevm_batches(batches) do
+ Enum.map(batches, fn batch ->
+ sequence_tx_hash =
+ if not is_nil(batch.sequence_transaction) do
+ batch.sequence_transaction.hash
+ end
+
+ verify_tx_hash =
+ if not is_nil(batch.verify_transaction) do
+ batch.verify_transaction.hash
+ end
+
+ %{
+ "number" => batch.number,
+ "status" => batch_status(batch),
+ "timestamp" => batch.timestamp,
+ "tx_count" => batch.l2_transactions_count,
+ "sequence_tx_hash" => sequence_tx_hash,
+ "verify_tx_hash" => verify_tx_hash
+ }
+ end)
+ end
+end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/block_view.ex
index e6355faf1945..2f215b478ced 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/block_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/block_view.ex
@@ -7,7 +7,7 @@ defmodule BlockScoutWeb.BlockView do
alias Explorer.Chain
alias Explorer.Chain.{Block, Wei}
alias Explorer.Chain.Block.Reward
- alias Explorer.Counters.{BlockBurnedFeeCounter, BlockPriorityFeeCounter}
+ alias Explorer.Counters.{BlockBurntFeeCounter, BlockPriorityFeeCounter}
@dialyzer :no_match
diff --git a/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex b/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex
index 43d66a29266d..bbbcdebe866e 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex
@@ -60,10 +60,7 @@ defmodule BlockScoutWeb.ChainView do
defp gas_prices do
case GasPriceOracle.get_gas_prices() do
{:ok, gas_prices} ->
- gas_prices
-
- nil ->
- nil
+ %{slow: gas_prices[:slow][:price], average: gas_prices[:average][:price], fast: gas_prices[:fast][:price]}
_ ->
nil
diff --git a/apps/block_scout_web/lib/block_scout_web/views/csv_export.ex b/apps/block_scout_web/lib/block_scout_web/views/csv_export.ex
index 083e11f53a25..a076e1d00afe 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/csv_export.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/csv_export.ex
@@ -1,8 +1,10 @@
defmodule BlockScoutWeb.CsvExportView do
use BlockScoutWeb, :view
+ alias BlockScoutWeb.Controller, as: BlockScoutWebController
alias Explorer.Chain
alias Explorer.Chain.Address
+ alias Explorer.Chain.CSVExport.Helper
defp type_display_name(type) do
case type do
@@ -14,14 +16,10 @@ defmodule BlockScoutWeb.CsvExportView do
end
end
+ defp type_download_path(nil), do: ""
+
defp type_download_path(type) do
- case type do
- "internal-transactions" -> :internal_transactions_csv
- "transactions" -> :transactions_csv
- "token-transfers" -> :token_transfers_csv
- "logs" -> :logs_csv
- _ -> ""
- end
+ type <> "-csv"
end
defp address_checksum(address_hash_string) do
diff --git a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex
index 5fc7f022b634..9cff33b73249 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex
@@ -1,8 +1,8 @@
defmodule BlockScoutWeb.LayoutView do
use BlockScoutWeb, :view
+ alias EthereumJSONRPC.Variant
alias Explorer.Chain
- alias Plug.Conn
alias Poison.Parser
import BlockScoutWeb.APIDocsView, only: [blockscout_url: 1]
@@ -63,11 +63,21 @@ defmodule BlockScoutWeb.LayoutView do
SocialMedia.links()
end
- def issue_link(conn) do
+ @doc """
+ Generates URL for new issue creation on Github
+ """
+ @spec issue_link() :: [term()]
+ def issue_link do
+ {os_family, os_name} = :os.type()
+
params = [
- labels: "BlockScout",
- body: issue_body(conn),
- title: subnetwork_title() <> ": "
+ template: "bug_report.yml",
+ labels: "triage",
+ "backend-version": version(),
+ "elixir-version": "Elixir #{System.version()} Erlang/OTP #{System.otp_release()}",
+ "os-version": "#{os_family} #{os_name}",
+ "archive-node-type": Variant.get(),
+ "additional-information": "The issue happened at #{subnetwork_title()} Blockscout instance"
]
issue_url = "#{Application.get_env(:block_scout_web, :footer)[:github_link]}/issues/new"
@@ -75,38 +85,6 @@ defmodule BlockScoutWeb.LayoutView do
[issue_url, "?", URI.encode_query(params)]
end
- defp issue_body(conn) do
- user_agent =
- case Conn.get_req_header(conn, "user-agent") do
- [] -> "unknown"
- [user_agent] -> if String.valid?(user_agent), do: user_agent, else: "unknown"
- _other -> "unknown"
- end
-
- """
- *Describe your issue here.*
-
- ### Environment
- * Elixir Version: #{System.version()}
- * Erlang Version: #{System.otp_release()}
- * BlockScout Version: #{version()}
-
- * User Agent: `#{user_agent}`
-
- ### Steps to reproduce
-
- *Tell us how to reproduce this issue. If possible, push up a branch to your fork with a regression test we can run to reproduce locally.*
-
- ### Expected Behaviour
-
- *Tell us what should happen.*
-
- ### Actual Behaviour
-
- *Tell us what happens instead.*
- """
- end
-
def version do
BlockScoutWeb.version()
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/nft_helper.ex b/apps/block_scout_web/lib/block_scout_web/views/nft_helper.ex
index aa3e528bbf35..a998729ebbd7 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/nft_helper.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/nft_helper.ex
@@ -81,6 +81,7 @@ defmodule BlockScoutWeb.NFTHelper do
defp ipfs_link(image_url, prefix) do
ipfs_uid = String.slice(image_url, String.length(prefix)..-1)
+
"https://ipfs.io/ipfs/" <> ipfs_uid
end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/robots_view.ex b/apps/block_scout_web/lib/block_scout_web/views/robots_view.ex
new file mode 100644
index 000000000000..9939ec3e684f
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/views/robots_view.ex
@@ -0,0 +1,10 @@
+defmodule BlockScoutWeb.RobotsView do
+ use BlockScoutWeb, :view
+
+ alias BlockScoutWeb.APIDocsView
+ alias Explorer.{Chain, PagingOptions}
+ alias Explorer.Chain.{Address, Token}
+
+ @limit 200
+ defp limit, do: @limit
+end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex
index 39297f295694..ad32647d14e0 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex
@@ -1,11 +1,17 @@
defmodule BlockScoutWeb.SmartContractView do
use BlockScoutWeb, :view
+ import Explorer.SmartContract.Reader, only: [zip_tuple_values_with_types: 2]
+
alias Explorer.Chain
alias Explorer.Chain.{Address, Transaction}
alias Explorer.Chain.Hash.Address, as: HashAddress
+ alias Explorer.Chain.SmartContract
+ alias Explorer.Chain.SmartContract.Proxy.EIP1167
alias Explorer.SmartContract.Helper
+ require Logger
+
def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs)
def queryable?(inputs) when is_nil(inputs), do: false
@@ -63,14 +69,14 @@ defmodule BlockScoutWeb.SmartContractView do
String.starts_with?(type, "address") ->
values =
value
- |> Enum.map_join(", ", &binary_to_utf_string(&1))
+ |> Enum.map_join(", ", &cast_address(&1))
render_array_type_value(type, values, fetch_name(names, index))
String.starts_with?(type, "bytes") ->
values =
value
- |> Enum.map_join(", ", &binary_to_utf_string(&1))
+ |> Enum.join(", ")
render_array_type_value(type, values, fetch_name(names, index))
@@ -105,6 +111,9 @@ defmodule BlockScoutWeb.SmartContractView do
def values_with_type(value, string, names, index, _components) when string in ["string", :string],
do: render_type_value("string", Helper.sanitize_input(value), fetch_name(names, index))
+ def values_with_type(value, "bytes" <> _ = bytes_type, names, index, _components),
+ do: render_type_value(bytes_type, Helper.sanitize_input(value), fetch_name(names, index))
+
def values_with_type(value, bytes, names, index, _components) when bytes in [:bytes],
do: render_type_value("bytes", Helper.sanitize_input(value), fetch_name(names, index))
@@ -112,11 +121,22 @@ defmodule BlockScoutWeb.SmartContractView do
do: render_type_value("bool", Helper.sanitize_input(to_string(value)), fetch_name(names, index))
def values_with_type(value, type, names, index, _components),
- do: render_type_value(type, Helper.sanitize_input(binary_to_utf_string(value)), fetch_name(names, index))
+ do: render_type_value(type, Helper.sanitize_input(value), fetch_name(names, index))
def values_with_type(value, :error, _components),
do: render_type_value("error", Helper.sanitize_input(value), "error")
+ def cast_address(value) do
+ case HashAddress.cast(value) do
+ {:ok, address} ->
+ to_string(address)
+
+ _ ->
+ Logger.warn(fn -> ["Error decoding address value: #{inspect(value)}"] end)
+ "(decoding error)"
+ end
+ end
+
defp fetch_name(nil, _index), do: nil
defp fetch_name([], _index), do: nil
@@ -137,84 +157,15 @@ defmodule BlockScoutWeb.SmartContractView do
end
defp tuple_to_array(value, type, names) do
- types_string =
- type
- |> String.slice(6..-2)
-
- types =
- if String.trim(types_string) == "" do
- []
- else
- types_string
- |> String.split(",")
- end
-
- {tuple_types, _} =
- types
- |> Enum.reduce({[], nil}, fn val, acc ->
- {arr, to_merge} = acc
-
- if to_merge do
- compose_array_if_to_merge(arr, val, to_merge)
- else
- compose_array_else(arr, val, to_merge)
- end
- end)
-
- values_list =
- value
- |> Tuple.to_list()
-
- values_types_list = Enum.zip(tuple_types, values_list)
-
- values_types_list
+ value
+ |> zip_tuple_values_with_types(type)
|> Enum.with_index()
|> Enum.map(fn {{type, value}, index} ->
values_with_type(value, type, fetch_name(names, index), 0)
end)
end
- defp compose_array_if_to_merge(arr, val, to_merge) do
- if count_string_symbols(val)["]"] > count_string_symbols(val)["["] do
- updated_arr = update_last_list_item(arr, val)
- {updated_arr, !to_merge}
- else
- updated_arr = update_last_list_item(arr, val)
- {updated_arr, to_merge}
- end
- end
-
- defp compose_array_else(arr, val, to_merge) do
- if count_string_symbols(val)["["] > count_string_symbols(val)["]"] do
- # credo:disable-for-next-line
- {arr ++ [val], !to_merge}
- else
- # credo:disable-for-next-line
- {arr ++ [val], to_merge}
- end
- end
-
- defp update_last_list_item(arr, new_val) do
- arr
- |> Enum.with_index()
- |> Enum.map(fn {item, index} ->
- if index == Enum.count(arr) - 1 do
- item <> "," <> new_val
- else
- item
- end
- end)
- end
-
- defp count_string_symbols(str) do
- str
- |> String.graphemes()
- |> Enum.reduce(%{"[" => 0, "]" => 0}, fn char, acc ->
- Map.update(acc, char, 1, &(&1 + 1))
- end)
- end
-
- defp binary_to_utf_string(item) do
+ def binary_to_utf_string(item) do
case Integer.parse(to_string(item)) do
{item_integer, ""} ->
to_string(item_integer)
@@ -229,11 +180,7 @@ defmodule BlockScoutWeb.SmartContractView do
end
defp add_0x(item) do
- if String.starts_with?(item, "0x") do
- item
- else
- "0x" <> Base.encode16(item, case: :lower)
- end
+ "0x" <> Base.encode16(item, case: :lower)
end
defp render_type_value(type, value, type) do
@@ -250,7 +197,7 @@ defmodule BlockScoutWeb.SmartContractView do
render_type_value(type, value_to_display, name)
end
- defp supplement_type_with_components(type, components) do
+ def supplement_type_with_components(type, components) do
if type == "tuple" && components do
types =
components
@@ -265,7 +212,7 @@ defmodule BlockScoutWeb.SmartContractView do
end
def decode_revert_reason(to_address, revert_reason, options \\ []) do
- smart_contract = Chain.address_hash_to_smart_contract(to_address, options)
+ smart_contract = SmartContract.address_hash_to_smart_contract(to_address, options)
Transaction.decoded_revert_reason(
%Transaction{to_address: %{smart_contract: smart_contract}, hash: to_address},
diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex
index 9bc2dbfe45e6..441ac1a0a4f1 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex
@@ -3,6 +3,7 @@ defmodule BlockScoutWeb.Tokens.OverviewView do
alias Explorer.{Chain, CustomContractsHelper}
alias Explorer.Chain.{Address, SmartContract, Token}
+ alias Explorer.Chain.SmartContract.Proxy
alias Explorer.SmartContract.{Helper, Writer}
alias BlockScoutWeb.{AccessHelper, CurrencyHelper, LayoutView}
@@ -53,11 +54,13 @@ defmodule BlockScoutWeb.Tokens.OverviewView do
def smart_contract_with_read_only_functions?(%Token{contract_address: %Address{smart_contract: nil}}), do: false
- def smart_contract_is_proxy?(%Token{contract_address: %Address{smart_contract: %SmartContract{} = smart_contract}}) do
- SmartContract.proxy_contract?(smart_contract)
+ def token_smart_contract_is_proxy?(%Token{
+ contract_address: %Address{smart_contract: %SmartContract{} = smart_contract}
+ }) do
+ Proxy.proxy_contract?(smart_contract)
end
- def smart_contract_is_proxy?(%Token{contract_address: %Address{smart_contract: nil}}), do: false
+ def token_smart_contract_is_proxy?(%Token{contract_address: %Address{smart_contract: nil}}), do: false
def smart_contract_with_write_functions?(%Token{
contract_address: %Address{smart_contract: %SmartContract{}} = address
diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_log_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_log_view.ex
index e494bbd88934..625ffb07a621 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/transaction_log_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_log_view.ex
@@ -2,10 +2,5 @@ defmodule BlockScoutWeb.TransactionLogView do
use BlockScoutWeb, :view
@dialyzer :no_match
- alias Explorer.Chain.Log
- import BlockScoutWeb.AddressView, only: [implementation_name: 1, primary_name: 1]
-
- def decode(log, transaction) do
- Log.decode(log, transaction)
- end
+ import BlockScoutWeb.AddressView, only: [decode: 2, implementation_name: 1, primary_name: 1]
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_state_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_state_view.ex
index 98b5f7e51dd1..69d6eaad9f27 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/transaction_state_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_state_view.ex
@@ -4,7 +4,7 @@ defmodule BlockScoutWeb.TransactionStateView do
alias Explorer.Chain
alias Explorer.Chain.Wei
- import BlockScoutWeb.Models.TransactionStateHelper, only: [from_loss: 1, has_diff?: 1, to_profit: 1]
+ import Explorer.Chain.Transaction.StateChange, only: [from_loss: 1, has_diff?: 1, to_profit: 1]
def not_negative?(%Wei{value: val}) do
not Decimal.negative?(val)
@@ -26,15 +26,15 @@ defmodule BlockScoutWeb.TransactionStateView do
has_diff?(from_loss(tx)) or has_diff?(to_profit(tx))
end
- def display_value(balance, :coin) do
+ def display_value(balance, :coin, _token_id) do
format_wei_value(balance, :ether)
end
- def display_value(balance, token_transfer) do
- render("_token_balance.html", transfer: token_transfer, balance: balance)
+ def display_value(balance, token_transfer, token_id) do
+ render("_token_balance.html", transfer: token_transfer, balance: balance, token_id: token_id)
end
- def display_nft(token_transfer) do
+ def display_erc_721(token_transfer) do
render(BlockScoutWeb.TransactionView, "_total_transfers.html", transfer: token_transfer)
end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
index 894e5448b6a7..ae493f391341 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
@@ -32,11 +32,13 @@ defmodule BlockScoutWeb.TransactionView do
defdelegate formatted_timestamp(block), to: BlockView
def block_number(%Transaction{block_number: nil}), do: gettext("Block Pending")
- def block_number(%Transaction{block: block}), do: [view_module: BlockView, partial: "_link.html", block: block]
+
+ def block_number(%Transaction{block_number: number, block_hash: hash}),
+ do: [view_module: BlockView, partial: "_link.html", block: %Block{number: number, hash: hash}]
+
def block_number(%Reward{block: block}), do: [view_module: BlockView, partial: "_link.html", block: block]
- def block_timestamp(%Transaction{block_number: nil, inserted_at: time}), do: time
- def block_timestamp(%Transaction{block: %Block{timestamp: time}}), do: time
+ def block_timestamp(%Transaction{} = transaction), do: Transaction.block_timestamp(transaction)
def block_timestamp(%Reward{block: %Block{timestamp: time}}), do: time
def value_transfer?(%Transaction{input: %{bytes: bytes}}) when bytes in [<<>>, nil] do
@@ -389,7 +391,8 @@ defmodule BlockScoutWeb.TransactionView do
end
def decoded_input_data(transaction) do
- Transaction.decoded_input_data(transaction, [])
+ {result, _, _} = Transaction.decoded_input_data(transaction, [])
+ result
end
def decoded_revert_reason(revert_reason, transaction, options) do
diff --git a/apps/block_scout_web/lib/block_scout_web/views/verified_contracts_view.ex b/apps/block_scout_web/lib/block_scout_web/views/verified_contracts_view.ex
index 90e1c621a41b..8a6e837d0ad4 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/verified_contracts_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/verified_contracts_view.ex
@@ -9,6 +9,7 @@ defmodule BlockScoutWeb.VerifiedContractsView do
case filter do
"solidity" -> gettext("Solidity")
"vyper" -> gettext("Vyper")
+ "yul" -> gettext("Yul")
_ -> gettext("All")
end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/wei_helper.ex b/apps/block_scout_web/lib/block_scout_web/views/wei_helper.ex
index 3b507d46557c..72c1c1ee1f3a 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/wei_helper.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/wei_helper.ex
@@ -52,8 +52,12 @@ defmodule BlockScoutWeb.WeiHelper do
...> )
"10"
"""
- @spec format_wei_value(Wei.t(), Wei.unit(), format_options()) :: String.t()
- def format_wei_value(%Wei{} = wei, unit, options \\ []) when unit in @valid_units do
+ @spec format_wei_value(Wei.t() | nil, Wei.unit(), format_options()) :: String.t() | nil
+ def format_wei_value(_wei, _unit, _options \\ [])
+
+ def format_wei_value(nil, _unit, _options), do: nil
+
+ def format_wei_value(%Wei{} = wei, unit, options) when unit in @valid_units do
converted_value =
wei
|> Wei.to(unit)
diff --git a/apps/block_scout_web/lib/block_scout_web/web_router.ex b/apps/block_scout_web/lib/block_scout_web/web_router.ex
index 4b3f7a20ae74..3f3dbed7d10e 100644
--- a/apps/block_scout_web/lib/block_scout_web/web_router.ex
+++ b/apps/block_scout_web/lib/block_scout_web/web_router.ex
@@ -493,16 +493,8 @@ defmodule BlockScoutWeb.WebRouter do
get("/csv-export", CsvExportController, :index)
- get("/transactions-csv", AddressTransactionController, :transactions_csv)
-
get("/token-autocomplete", ChainController, :token_autocomplete)
- get("/token-transfers-csv", AddressTransactionController, :token_transfers_csv)
-
- get("/internal-transactions-csv", AddressTransactionController, :internal_transactions_csv)
-
- get("/logs-csv", AddressTransactionController, :logs_csv)
-
get("/chain-blocks", ChainController, :chain_blocks, as: :chain_blocks)
get("/token-counters", Tokens.TokenController, :token_counters)
diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs
index 18d2e6ee58a2..741558064926 100644
--- a/apps/block_scout_web/mix.exs
+++ b/apps/block_scout_web/mix.exs
@@ -11,7 +11,7 @@ defmodule BlockScoutWeb.Mixfile do
deps_path: "../../deps",
description: "Web interface for BlockScout.",
dialyzer: [
- plt_add_deps: :transitive,
+ plt_add_deps: :app_tree,
ignore_warnings: "../../.dialyzer-ignore"
],
elixir: "~> 1.13",
@@ -23,7 +23,8 @@ defmodule BlockScoutWeb.Mixfile do
dialyzer: :test
],
start_permanent: Mix.env() == :prod,
- version: "5.1.5"
+ version: "6.0.0",
+ xref: [exclude: [Explorer.Chain.Zkevm.Reader]]
]
end
@@ -83,7 +84,7 @@ defmodule BlockScoutWeb.Mixfile do
# HTML CSS selectors for Phoenix controller tests
{:floki, "~> 0.31"},
{:flow, "~> 1.2"},
- {:gettext, "~> 0.22.0"},
+ {:gettext, "~> 0.24.0"},
{:hammer, "~> 6.0"},
{:httpoison, "~> 2.0"},
{:indexer, in_umbrella: true, runtime: false},
@@ -95,7 +96,7 @@ defmodule BlockScoutWeb.Mixfile do
{:math, "~> 0.7.0"},
{:mock, "~> 0.3.0", only: [:test], runtime: false},
{:number, "~> 1.0.1"},
- {:phoenix, "== 1.5.13"},
+ {:phoenix, "== 1.5.14"},
{:phoenix_ecto, "~> 4.1"},
{:phoenix_html, "== 3.0.4"},
{:phoenix_live_reload, "~> 1.2", only: [:dev]},
@@ -128,10 +129,11 @@ defmodule BlockScoutWeb.Mixfile do
{:wallaby, "~> 0.30", only: :test, runtime: false},
# `:cowboy` `~> 2.0` and Phoenix 1.4 compatibility
{:websocket_client, git: "https://github.com/blockscout/websocket_client.git", branch: "master", override: true},
- {:ex_json_schema, "~> 0.9.1"},
+ {:ex_json_schema, "~> 0.10.1"},
{:ueberauth, "~> 0.7"},
{:ueberauth_auth0, "~> 2.0"},
- {:bureaucrat, "~> 0.2.9", only: :test}
+ {:bureaucrat, "~> 0.2.9", only: :test},
+ {:logger_json, "~> 5.1"}
]
end
diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot
index 1d3e636fbb14..224aa1df7789 100644
--- a/apps/block_scout_web/priv/gettext/default.pot
+++ b/apps/block_scout_web/priv/gettext/default.pot
@@ -68,7 +68,7 @@ msgstr ""
msgid "%{subnetwork} Explorer - BlockScout"
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:369
+#: lib/block_scout_web/views/transaction_view.ex:371
#, elixir-autogen, elixir-format
msgid "(Awaiting internal transactions for status)"
msgstr ""
@@ -86,7 +86,7 @@ msgstr ""
msgid ") may be added for each contract. Click the Add Library button to add an additional one."
msgstr ""
-#: lib/block_scout_web/templates/layout/app.html.eex:90
+#: lib/block_scout_web/templates/layout/app.html.eex:93
#, elixir-autogen, elixir-format
msgid "- We're indexing this chain right now. Some of the counts may be inaccurate."
msgstr ""
@@ -122,7 +122,7 @@ msgstr ""
msgid "A block producer who successfully included the block onto the blockchain."
msgstr ""
-#: lib/block_scout_web/templates/layout/app.html.eex:97
+#: lib/block_scout_web/templates/layout/app.html.eex:100
#, elixir-autogen, elixir-format
msgid "A confirmation email was sent to"
msgstr ""
@@ -263,9 +263,9 @@ msgstr ""
#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:4
#: lib/block_scout_web/templates/tokens/index.html.eex:34
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:20
-#: lib/block_scout_web/templates/transaction_state/index.html.eex:29
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:54
-#: lib/block_scout_web/views/address_view.ex:107
+#: lib/block_scout_web/templates/transaction_state/index.html.eex:34
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:60
+#: lib/block_scout_web/views/address_view.ex:109
#, elixir-autogen, elixir-format
msgid "Address"
msgstr ""
@@ -326,7 +326,7 @@ msgstr ""
#: lib/block_scout_web/views/address_internal_transaction_view.ex:11
#: lib/block_scout_web/views/address_token_transfer_view.ex:11
#: lib/block_scout_web/views/address_transaction_view.ex:11
-#: lib/block_scout_web/views/verified_contracts_view.ex:12
+#: lib/block_scout_web/views/verified_contracts_view.ex:13
#, elixir-autogen, elixir-format
msgid "All"
msgstr ""
@@ -424,17 +424,17 @@ msgstr ""
#: lib/block_scout_web/templates/account/watchlist/show.html.eex:24
#: lib/block_scout_web/templates/address/overview.html.eex:150
#: lib/block_scout_web/templates/address_token/overview.html.eex:51
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:57
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:63
#, elixir-autogen, elixir-format
msgid "Balance"
msgstr ""
-#: lib/block_scout_web/templates/transaction_state/index.html.eex:35
+#: lib/block_scout_web/templates/transaction_state/index.html.eex:40
#, elixir-autogen, elixir-format
msgid "Balance after"
msgstr ""
-#: lib/block_scout_web/templates/transaction_state/index.html.eex:32
+#: lib/block_scout_web/templates/transaction_state/index.html.eex:37
#, elixir-autogen, elixir-format
msgid "Balance before"
msgstr ""
@@ -556,7 +556,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:56
#: lib/block_scout_web/templates/address/overview.html.eex:275
#: lib/block_scout_web/templates/address_validation/index.html.eex:11
-#: lib/block_scout_web/views/address_view.ex:384
+#: lib/block_scout_web/views/address_view.ex:386
#, elixir-autogen, elixir-format
msgid "Blocks Validated"
msgstr ""
@@ -587,7 +587,7 @@ msgstr ""
msgid "CRC Worth"
msgstr ""
-#: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:2
+#: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:4
#, elixir-autogen, elixir-format
msgid "CSV"
msgstr ""
@@ -616,7 +616,7 @@ msgstr ""
msgid "Cancel"
msgstr ""
-#: lib/block_scout_web/templates/transaction_state/index.html.eex:38
+#: lib/block_scout_web/templates/transaction_state/index.html.eex:43
#, elixir-autogen, elixir-format
msgid "Change"
msgstr ""
@@ -656,13 +656,13 @@ msgstr ""
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149
-#: lib/block_scout_web/views/address_view.ex:377
+#: lib/block_scout_web/views/address_view.ex:379
#, elixir-autogen, elixir-format
msgid "Code"
msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:42
-#: lib/block_scout_web/views/address_view.ex:383
+#: lib/block_scout_web/views/address_view.ex:385
#, elixir-autogen, elixir-format
msgid "Coin Balance History"
msgstr ""
@@ -683,7 +683,7 @@ msgid "Company website"
msgstr ""
#: lib/block_scout_web/templates/address_contract_verification_common_fields/_compiler_field.html.eex:3
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:63
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:69
#, elixir-autogen, elixir-format
msgid "Compiler"
msgstr ""
@@ -698,7 +698,7 @@ msgstr ""
msgid "Compiler version"
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:362
+#: lib/block_scout_web/views/transaction_view.ex:364
#, elixir-autogen, elixir-format
msgid "Confirmed"
msgstr ""
@@ -752,7 +752,7 @@ msgstr ""
msgid "Constructor Arguments"
msgstr ""
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:72
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:78
#, elixir-autogen, elixir-format
msgid "Constructor args"
msgstr ""
@@ -771,24 +771,24 @@ msgstr ""
#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:18
#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:29
#: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex:3
-#: lib/block_scout_web/views/address_view.ex:105
+#: lib/block_scout_web/views/address_view.ex:107
#, elixir-autogen, elixir-format
msgid "Contract Address"
msgstr ""
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16
-#: lib/block_scout_web/views/address_view.ex:45
-#: lib/block_scout_web/views/address_view.ex:79
+#: lib/block_scout_web/views/address_view.ex:47
+#: lib/block_scout_web/views/address_view.ex:81
#, elixir-autogen, elixir-format
msgid "Contract Address Pending"
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:477
+#: lib/block_scout_web/views/transaction_view.ex:480
#, elixir-autogen, elixir-format
msgid "Contract Call"
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:474
+#: lib/block_scout_web/views/transaction_view.ex:477
#, elixir-autogen, elixir-format
msgid "Contract Creation"
msgstr ""
@@ -817,7 +817,7 @@ msgstr ""
msgid "Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB"
msgstr ""
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:41
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:47
#, elixir-autogen, elixir-format
msgid "Contract name or address"
msgstr ""
@@ -1049,7 +1049,7 @@ msgstr ""
msgid "Daily Transactions"
msgstr ""
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:101
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:98
#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7
#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:23
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:121
@@ -1084,7 +1084,7 @@ msgstr ""
msgid "Decoded"
msgstr ""
-#: lib/block_scout_web/views/address_view.ex:378
+#: lib/block_scout_web/views/address_view.ex:380
#, elixir-autogen, elixir-format
msgid "Decompiled Code"
msgstr ""
@@ -1145,7 +1145,8 @@ msgstr ""
msgid "Displaying the init data provided of the creating transaction."
msgstr ""
-#: lib/block_scout_web/templates/csv_export/index.html.eex:32
+#: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:4
+#: lib/block_scout_web/templates/csv_export/index.html.eex:33
#, elixir-autogen, elixir-format
msgid "Download"
msgstr ""
@@ -1185,12 +1186,12 @@ msgstr ""
msgid "EIP-1167"
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:220
+#: lib/block_scout_web/views/transaction_view.ex:222
#, elixir-autogen, elixir-format
msgid "ERC-1155 "
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:218
+#: lib/block_scout_web/views/transaction_view.ex:220
#, elixir-autogen, elixir-format
msgid "ERC-20 "
msgstr ""
@@ -1200,7 +1201,7 @@ msgstr ""
msgid "ERC-20 tokens (beta)"
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:219
+#: lib/block_scout_web/views/transaction_view.ex:221
#, elixir-autogen, elixir-format
msgid "ERC-721 "
msgstr ""
@@ -1291,12 +1292,12 @@ msgstr ""
msgid "Error trying to fetch balances."
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:373
+#: lib/block_scout_web/views/transaction_view.ex:375
#, elixir-autogen, elixir-format
msgid "Error: %{reason}"
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:371
+#: lib/block_scout_web/views/transaction_view.ex:373
#, elixir-autogen, elixir-format
msgid "Error: (Awaiting internal transactions for reason)"
msgstr ""
@@ -1484,7 +1485,7 @@ msgstr ""
#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22
#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:38
#: lib/block_scout_web/views/block_view.ex:22
-#: lib/block_scout_web/views/wei_helper.ex:77
+#: lib/block_scout_web/views/wei_helper.ex:81
#, elixir-autogen, elixir-format
msgid "Gwei"
msgstr ""
@@ -1600,8 +1601,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6
-#: lib/block_scout_web/views/address_view.ex:374
-#: lib/block_scout_web/views/transaction_view.ex:532
+#: lib/block_scout_web/views/address_view.ex:376
+#: lib/block_scout_web/views/transaction_view.ex:535
#, elixir-autogen, elixir-format
msgid "Internal Transactions"
msgstr ""
@@ -1613,7 +1614,7 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:16
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:19
-#: lib/block_scout_web/views/tokens/overview_view.ex:42
+#: lib/block_scout_web/views/tokens/overview_view.ex:43
#, elixir-autogen, elixir-format
msgid "Inventory"
msgstr ""
@@ -1717,8 +1718,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_logs/index.html.eex:10
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8
-#: lib/block_scout_web/views/address_view.ex:385
-#: lib/block_scout_web/views/transaction_view.ex:533
+#: lib/block_scout_web/views/address_view.ex:387
+#: lib/block_scout_web/views/transaction_view.ex:536
#, elixir-autogen, elixir-format
msgid "Logs"
msgstr ""
@@ -1731,12 +1732,12 @@ msgstr ""
#: lib/block_scout_web/templates/chain/show.html.eex:53
#: lib/block_scout_web/templates/layout/app.html.eex:50
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:85
-#: lib/block_scout_web/views/address_view.ex:145
+#: lib/block_scout_web/views/address_view.ex:147
#, elixir-autogen, elixir-format
msgid "Market Cap"
msgstr ""
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:78
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:84
#, elixir-autogen, elixir-format
msgid "Market cap"
msgstr ""
@@ -1751,7 +1752,7 @@ msgstr ""
msgid "Max Priority Fee per Gas"
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:325
+#: lib/block_scout_web/views/transaction_view.ex:327
#, elixir-autogen, elixir-format
msgid "Max of"
msgstr ""
@@ -1996,7 +1997,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:40
#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:32
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:69
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:75
#, elixir-autogen, elixir-format
msgid "Optimization"
msgstr ""
@@ -2063,8 +2064,8 @@ msgid "Parent Hash"
msgstr ""
#: lib/block_scout_web/templates/layout/_topnav.html.eex:63
-#: lib/block_scout_web/views/transaction_view.ex:368
-#: lib/block_scout_web/views/transaction_view.ex:406
+#: lib/block_scout_web/views/transaction_view.ex:370
+#: lib/block_scout_web/views/transaction_view.ex:409
#, elixir-autogen, elixir-format
msgid "Pending"
msgstr ""
@@ -2081,7 +2082,7 @@ msgid "Play"
msgstr ""
#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:22
-#: lib/block_scout_web/templates/layout/app.html.eex:97
+#: lib/block_scout_web/templates/layout/app.html.eex:100
#, elixir-autogen, elixir-format
msgid "Please confirm your email address to use the My Account feature."
msgstr ""
@@ -2200,22 +2201,22 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24
#: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:1
-#: lib/block_scout_web/views/transaction_view.ex:534
+#: lib/block_scout_web/views/transaction_view.ex:537
#, elixir-autogen, elixir-format
msgid "Raw Trace"
msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:89
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27
-#: lib/block_scout_web/views/address_view.ex:379
-#: lib/block_scout_web/views/tokens/overview_view.ex:41
+#: lib/block_scout_web/views/address_view.ex:381
+#: lib/block_scout_web/views/tokens/overview_view.ex:42
#, elixir-autogen, elixir-format
msgid "Read Contract"
msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:96
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41
-#: lib/block_scout_web/views/address_view.ex:380
+#: lib/block_scout_web/views/address_view.ex:382
#, elixir-autogen, elixir-format
msgid "Read Proxy"
msgstr ""
@@ -2261,7 +2262,7 @@ msgstr ""
msgid "Request to edit a public tag/label"
msgstr ""
-#: lib/block_scout_web/templates/layout/app.html.eex:97
+#: lib/block_scout_web/templates/layout/app.html.eex:100
#, elixir-autogen, elixir-format
msgid "Resend verification email"
msgstr ""
@@ -2468,9 +2469,9 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/index.html.eex:25
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:13
#: lib/block_scout_web/templates/transaction_log/index.html.eex:15
-#: lib/block_scout_web/templates/transaction_state/index.html.eex:8
+#: lib/block_scout_web/templates/transaction_state/index.html.eex:13
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:14
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:45
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:51
#: lib/block_scout_web/templates/withdrawal/index.html.eex:14
#, elixir-autogen, elixir-format
msgid "Something went wrong, click to reload."
@@ -2513,7 +2514,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:29
#: lib/block_scout_web/templates/transaction_state/index.html.eex:6
-#: lib/block_scout_web/views/transaction_view.ex:535
+#: lib/block_scout_web/views/transaction_view.ex:538
#, elixir-autogen, elixir-format
msgid "State changes"
msgstr ""
@@ -2539,7 +2540,7 @@ msgid "Submit an Issue"
msgstr ""
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8
-#: lib/block_scout_web/views/transaction_view.ex:370
+#: lib/block_scout_web/views/transaction_view.ex:372
#, elixir-autogen, elixir-format
msgid "Success"
msgstr ""
@@ -2581,7 +2582,7 @@ msgstr ""
msgid "The block height of a particular block is defined as the number of blocks preceding it in the blockchain."
msgstr ""
-#: lib/block_scout_web/templates/transaction_state/index.html.eex:13
+#: lib/block_scout_web/templates/transaction_state/index.html.eex:18
#, elixir-autogen, elixir-format
msgid "The changes from this transaction have not yet happened since the transaction is still pending."
msgstr ""
@@ -2642,11 +2643,6 @@ msgstr ""
msgid "The total gas amount used in the block and its percentage of gas filled in the block."
msgstr ""
-#: lib/block_scout_web/templates/withdrawal/index.html.eex:11
-#, elixir-autogen, elixir-format
-msgid "There are %{withdrawals_count} withdrawals total that withdrawn %{withdrawals_sum}"
-msgstr ""
-
#: lib/block_scout_web/templates/address_validation/index.html.eex:16
#, elixir-autogen, elixir-format
msgid "There are no blocks validated by this address."
@@ -2729,7 +2725,7 @@ msgstr ""
msgid "There are no transfers for this Token."
msgstr ""
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:93
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:99
#, elixir-autogen, elixir-format
msgid "There are no verified contracts."
msgstr ""
@@ -2800,7 +2796,7 @@ msgstr ""
msgid "This page is no longer explorable! If you are lost, use the search bar to find what you are looking for."
msgstr ""
-#: lib/block_scout_web/templates/transaction_state/index.html.eex:17
+#: lib/block_scout_web/templates/transaction_state/index.html.eex:22
#, elixir-autogen, elixir-format
msgid "This transaction hasn't changed state."
msgstr ""
@@ -2853,13 +2849,13 @@ msgid "Token"
msgstr ""
#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3
-#: lib/block_scout_web/views/transaction_view.ex:468
+#: lib/block_scout_web/views/transaction_view.ex:471
#, elixir-autogen, elixir-format
msgid "Token Burning"
msgstr ""
#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7
-#: lib/block_scout_web/views/transaction_view.ex:469
+#: lib/block_scout_web/views/transaction_view.ex:472
#, elixir-autogen, elixir-format
msgid "Token Creation"
msgstr ""
@@ -2874,7 +2870,7 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16
#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:11
-#: lib/block_scout_web/views/tokens/overview_view.ex:40
+#: lib/block_scout_web/views/tokens/overview_view.ex:41
#, elixir-autogen, elixir-format
msgid "Token Holders"
msgstr ""
@@ -2887,14 +2883,14 @@ msgid "Token ID"
msgstr ""
#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5
-#: lib/block_scout_web/views/transaction_view.ex:467
+#: lib/block_scout_web/views/transaction_view.ex:470
#, elixir-autogen, elixir-format
msgid "Token Minting"
msgstr ""
#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:9
#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:11
-#: lib/block_scout_web/views/transaction_view.ex:470
+#: lib/block_scout_web/views/transaction_view.ex:473
#, elixir-autogen, elixir-format
msgid "Token Transfer"
msgstr ""
@@ -2907,10 +2903,10 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:15
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7
-#: lib/block_scout_web/views/address_view.ex:376
+#: lib/block_scout_web/views/address_view.ex:378
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:114
-#: lib/block_scout_web/views/tokens/overview_view.ex:39
-#: lib/block_scout_web/views/transaction_view.ex:531
+#: lib/block_scout_web/views/tokens/overview_view.ex:40
+#: lib/block_scout_web/views/transaction_view.ex:534
#, elixir-autogen, elixir-format
msgid "Token Transfers"
msgstr ""
@@ -2931,7 +2927,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13
#: lib/block_scout_web/templates/layout/_topnav.html.eex:84
#: lib/block_scout_web/templates/tokens/index.html.eex:10
-#: lib/block_scout_web/views/address_view.ex:373
+#: lib/block_scout_web/views/address_view.ex:375
#, elixir-autogen, elixir-format
msgid "Tokens"
msgstr ""
@@ -2966,7 +2962,7 @@ msgstr ""
msgid "Topic"
msgstr ""
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:71
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:68
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:91
#, elixir-autogen, elixir-format
msgid "Topics"
@@ -3026,7 +3022,7 @@ msgstr ""
#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:11
#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:23
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:19
-#: lib/block_scout_web/views/transaction_view.ex:480
+#: lib/block_scout_web/views/transaction_view.ex:483
#, elixir-autogen, elixir-format
msgid "Transaction"
msgstr ""
@@ -3095,6 +3091,7 @@ msgid "Transaction type, introduced in EIP-2718."
msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:7
+#: lib/block_scout_web/templates/address/_tile.html.eex:31
#: lib/block_scout_web/templates/address/overview.html.eex:186
#: lib/block_scout_web/templates/address/overview.html.eex:192
#: lib/block_scout_web/templates/address/overview.html.eex:200
@@ -3103,7 +3100,7 @@ msgstr ""
#: lib/block_scout_web/templates/block/overview.html.eex:80
#: lib/block_scout_web/templates/chain/show.html.eex:214
#: lib/block_scout_web/templates/layout/_topnav.html.eex:49
-#: lib/block_scout_web/views/address_view.ex:375
+#: lib/block_scout_web/views/address_view.ex:377
#, elixir-autogen, elixir-format
msgid "Transactions"
msgstr ""
@@ -3113,11 +3110,6 @@ msgstr ""
msgid "Transactions and address of creation."
msgstr ""
-#: lib/block_scout_web/templates/address/_tile.html.eex:31
-#, elixir-autogen, elixir-format
-msgid "Transactions sent"
-msgstr ""
-
#: lib/block_scout_web/templates/address/overview.html.eex:213
#: lib/block_scout_web/templates/address/overview.html.eex:219
#: lib/block_scout_web/templates/address/overview.html.eex:227
@@ -3148,7 +3140,7 @@ msgstr ""
msgid "Tx/day"
msgstr ""
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:60
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:66
#, elixir-autogen, elixir-format
msgid "Txns"
msgstr ""
@@ -3185,7 +3177,7 @@ msgstr ""
msgid "Uncles"
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:361
+#: lib/block_scout_web/views/transaction_view.ex:363
#, elixir-autogen, elixir-format
msgid "Unconfirmed"
msgstr ""
@@ -3265,7 +3257,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:17
#: lib/block_scout_web/templates/address_write_contract/index.html.eex:15
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:75
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:81
#, elixir-autogen, elixir-format
msgid "Verified"
msgstr ""
@@ -3322,7 +3314,7 @@ msgid "Verify the contract "
msgstr ""
#: lib/block_scout_web/templates/layout/_footer.html.eex:93
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:66
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:72
#, elixir-autogen, elixir-format
msgid "Version"
msgstr ""
@@ -3452,7 +3444,7 @@ msgstr ""
msgid "We recommend using flattened code. This is necessary if your code utilizes a library or inherits dependencies. Use the"
msgstr ""
-#: lib/block_scout_web/views/wei_helper.ex:76
+#: lib/block_scout_web/views/wei_helper.ex:80
#, elixir-autogen, elixir-format
msgid "Wei"
msgstr ""
@@ -3473,14 +3465,14 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:103
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34
-#: lib/block_scout_web/views/address_view.ex:381
+#: lib/block_scout_web/views/address_view.ex:383
#, elixir-autogen, elixir-format
msgid "Write Contract"
msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:110
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48
-#: lib/block_scout_web/views/address_view.ex:382
+#: lib/block_scout_web/views/address_view.ex:384
#, elixir-autogen, elixir-format
msgid "Write Proxy"
msgstr ""
@@ -3549,16 +3541,6 @@ msgstr ""
msgid "balance of the address"
msgstr ""
-#: lib/block_scout_web/templates/transaction/overview.html.eex:437
-#, elixir-autogen, elixir-format
-msgid "burned for this transaction. Equals Block Base Fee per Gas * Gas Used."
-msgstr ""
-
-#: lib/block_scout_web/templates/block/overview.html.eex:215
-#, elixir-autogen, elixir-format
-msgid "burned from transactions included in the block (Base fee (per unit of gas) * Gas Used)."
-msgstr ""
-
#: lib/block_scout_web/templates/address_contract/index.html.eex:28
#, elixir-autogen, elixir-format
msgid "button"
@@ -3589,7 +3571,7 @@ msgstr ""
msgid "fallback"
msgstr ""
-#: lib/block_scout_web/views/address_contract_view.ex:25
+#: lib/block_scout_web/views/address_contract_view.ex:30
#, elixir-autogen, elixir-format
msgid "false"
msgstr ""
@@ -3616,7 +3598,7 @@ msgstr ""
msgid "of"
msgstr ""
-#: lib/block_scout_web/templates/layout/app.html.eex:97
+#: lib/block_scout_web/templates/layout/app.html.eex:100
#, elixir-autogen, elixir-format
msgid "on sign up. Didn’t receive?"
msgstr ""
@@ -3645,7 +3627,7 @@ msgstr ""
msgid "string"
msgstr ""
-#: lib/block_scout_web/views/address_contract_view.ex:24
+#: lib/block_scout_web/views/address_contract_view.ex:29
#, elixir-autogen, elixir-format
msgid "true"
msgstr ""
@@ -3664,3 +3646,39 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "New Smart Contract Verification via metadata JSON"
msgstr ""
+
+#: lib/block_scout_web/templates/withdrawal/index.html.eex:11
+#, elixir-autogen, elixir-format
+msgid "%{withdrawals_count} withdrawals processed and %{withdrawals_sum} withdrawn."
+msgstr ""
+
+#: lib/block_scout_web/templates/csv_export/index.html.eex:14
+#, elixir-autogen, elixir-format
+msgid "Export"
+msgstr ""
+
+#: lib/block_scout_web/templates/csv_export/index.html.eex:14
+#, elixir-autogen, elixir-format
+msgid "for address"
+msgstr ""
+
+#: lib/block_scout_web/templates/csv_export/index.html.eex:17
+#, elixir-autogen, elixir-format
+msgid "to CSV file"
+msgstr ""
+
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:38
+#: lib/block_scout_web/views/verified_contracts_view.ex:12
+#, elixir-autogen, elixir-format
+msgid "Yul"
+msgstr ""
+
+#: lib/block_scout_web/templates/transaction/overview.html.eex:437
+#, elixir-autogen, elixir-format
+msgid "burnt for this transaction. Equals Block Base Fee per Gas * Gas Used."
+msgstr ""
+
+#: lib/block_scout_web/templates/block/overview.html.eex:215
+#, elixir-autogen, elixir-format
+msgid "burnt from transactions included in the block (Base fee (per unit of gas) * Gas Used)."
+msgstr ""
diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
index 374b4a60914d..4d1362f4d282 100644
--- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
+++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
@@ -68,7 +68,7 @@ msgstr ""
msgid "%{subnetwork} Explorer - BlockScout"
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:369
+#: lib/block_scout_web/views/transaction_view.ex:371
#, elixir-autogen, elixir-format
msgid "(Awaiting internal transactions for status)"
msgstr ""
@@ -86,7 +86,7 @@ msgstr ""
msgid ") may be added for each contract. Click the Add Library button to add an additional one."
msgstr ""
-#: lib/block_scout_web/templates/layout/app.html.eex:90
+#: lib/block_scout_web/templates/layout/app.html.eex:93
#, elixir-autogen, elixir-format
msgid "- We're indexing this chain right now. Some of the counts may be inaccurate."
msgstr ""
@@ -122,7 +122,7 @@ msgstr ""
msgid "A block producer who successfully included the block onto the blockchain."
msgstr ""
-#: lib/block_scout_web/templates/layout/app.html.eex:97
+#: lib/block_scout_web/templates/layout/app.html.eex:100
#, elixir-autogen, elixir-format
msgid "A confirmation email was sent to"
msgstr ""
@@ -263,9 +263,9 @@ msgstr ""
#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:4
#: lib/block_scout_web/templates/tokens/index.html.eex:34
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:20
-#: lib/block_scout_web/templates/transaction_state/index.html.eex:29
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:54
-#: lib/block_scout_web/views/address_view.ex:107
+#: lib/block_scout_web/templates/transaction_state/index.html.eex:34
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:60
+#: lib/block_scout_web/views/address_view.ex:109
#, elixir-autogen, elixir-format
msgid "Address"
msgstr ""
@@ -326,7 +326,7 @@ msgstr ""
#: lib/block_scout_web/views/address_internal_transaction_view.ex:11
#: lib/block_scout_web/views/address_token_transfer_view.ex:11
#: lib/block_scout_web/views/address_transaction_view.ex:11
-#: lib/block_scout_web/views/verified_contracts_view.ex:12
+#: lib/block_scout_web/views/verified_contracts_view.ex:13
#, elixir-autogen, elixir-format
msgid "All"
msgstr ""
@@ -424,17 +424,17 @@ msgstr ""
#: lib/block_scout_web/templates/account/watchlist/show.html.eex:24
#: lib/block_scout_web/templates/address/overview.html.eex:150
#: lib/block_scout_web/templates/address_token/overview.html.eex:51
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:57
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:63
#, elixir-autogen, elixir-format
msgid "Balance"
msgstr ""
-#: lib/block_scout_web/templates/transaction_state/index.html.eex:35
+#: lib/block_scout_web/templates/transaction_state/index.html.eex:40
#, elixir-autogen, elixir-format
msgid "Balance after"
msgstr ""
-#: lib/block_scout_web/templates/transaction_state/index.html.eex:32
+#: lib/block_scout_web/templates/transaction_state/index.html.eex:37
#, elixir-autogen, elixir-format
msgid "Balance before"
msgstr ""
@@ -556,7 +556,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:56
#: lib/block_scout_web/templates/address/overview.html.eex:275
#: lib/block_scout_web/templates/address_validation/index.html.eex:11
-#: lib/block_scout_web/views/address_view.ex:384
+#: lib/block_scout_web/views/address_view.ex:386
#, elixir-autogen, elixir-format
msgid "Blocks Validated"
msgstr ""
@@ -587,7 +587,7 @@ msgstr ""
msgid "CRC Worth"
msgstr ""
-#: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:2
+#: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:4
#, elixir-autogen, elixir-format
msgid "CSV"
msgstr ""
@@ -616,7 +616,7 @@ msgstr ""
msgid "Cancel"
msgstr ""
-#: lib/block_scout_web/templates/transaction_state/index.html.eex:38
+#: lib/block_scout_web/templates/transaction_state/index.html.eex:43
#, elixir-autogen, elixir-format
msgid "Change"
msgstr ""
@@ -656,13 +656,13 @@ msgstr ""
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149
-#: lib/block_scout_web/views/address_view.ex:377
+#: lib/block_scout_web/views/address_view.ex:379
#, elixir-autogen, elixir-format
msgid "Code"
msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:42
-#: lib/block_scout_web/views/address_view.ex:383
+#: lib/block_scout_web/views/address_view.ex:385
#, elixir-autogen, elixir-format
msgid "Coin Balance History"
msgstr ""
@@ -683,7 +683,7 @@ msgid "Company website"
msgstr ""
#: lib/block_scout_web/templates/address_contract_verification_common_fields/_compiler_field.html.eex:3
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:63
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:69
#, elixir-autogen, elixir-format
msgid "Compiler"
msgstr ""
@@ -698,7 +698,7 @@ msgstr ""
msgid "Compiler version"
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:362
+#: lib/block_scout_web/views/transaction_view.ex:364
#, elixir-autogen, elixir-format
msgid "Confirmed"
msgstr ""
@@ -752,7 +752,7 @@ msgstr ""
msgid "Constructor Arguments"
msgstr ""
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:72
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:78
#, elixir-autogen, elixir-format
msgid "Constructor args"
msgstr ""
@@ -771,24 +771,24 @@ msgstr ""
#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:18
#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:29
#: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex:3
-#: lib/block_scout_web/views/address_view.ex:105
+#: lib/block_scout_web/views/address_view.ex:107
#, elixir-autogen, elixir-format
msgid "Contract Address"
msgstr ""
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16
-#: lib/block_scout_web/views/address_view.ex:45
-#: lib/block_scout_web/views/address_view.ex:79
+#: lib/block_scout_web/views/address_view.ex:47
+#: lib/block_scout_web/views/address_view.ex:81
#, elixir-autogen, elixir-format
msgid "Contract Address Pending"
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:477
+#: lib/block_scout_web/views/transaction_view.ex:480
#, elixir-autogen, elixir-format
msgid "Contract Call"
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:474
+#: lib/block_scout_web/views/transaction_view.ex:477
#, elixir-autogen, elixir-format
msgid "Contract Creation"
msgstr ""
@@ -817,7 +817,7 @@ msgstr ""
msgid "Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB"
msgstr ""
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:41
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:47
#, elixir-autogen, elixir-format
msgid "Contract name or address"
msgstr ""
@@ -1049,7 +1049,7 @@ msgstr ""
msgid "Daily Transactions"
msgstr ""
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:101
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:98
#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7
#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:23
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:121
@@ -1084,7 +1084,7 @@ msgstr ""
msgid "Decoded"
msgstr ""
-#: lib/block_scout_web/views/address_view.ex:378
+#: lib/block_scout_web/views/address_view.ex:380
#, elixir-autogen, elixir-format
msgid "Decompiled Code"
msgstr ""
@@ -1145,7 +1145,8 @@ msgstr ""
msgid "Displaying the init data provided of the creating transaction."
msgstr ""
-#: lib/block_scout_web/templates/csv_export/index.html.eex:32
+#: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:4
+#: lib/block_scout_web/templates/csv_export/index.html.eex:33
#, elixir-autogen, elixir-format
msgid "Download"
msgstr ""
@@ -1185,12 +1186,12 @@ msgstr ""
msgid "EIP-1167"
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:220
+#: lib/block_scout_web/views/transaction_view.ex:222
#, elixir-autogen, elixir-format
msgid "ERC-1155 "
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:218
+#: lib/block_scout_web/views/transaction_view.ex:220
#, elixir-autogen, elixir-format
msgid "ERC-20 "
msgstr ""
@@ -1200,7 +1201,7 @@ msgstr ""
msgid "ERC-20 tokens (beta)"
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:219
+#: lib/block_scout_web/views/transaction_view.ex:221
#, elixir-autogen, elixir-format
msgid "ERC-721 "
msgstr ""
@@ -1291,12 +1292,12 @@ msgstr ""
msgid "Error trying to fetch balances."
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:373
+#: lib/block_scout_web/views/transaction_view.ex:375
#, elixir-autogen, elixir-format
msgid "Error: %{reason}"
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:371
+#: lib/block_scout_web/views/transaction_view.ex:373
#, elixir-autogen, elixir-format
msgid "Error: (Awaiting internal transactions for reason)"
msgstr ""
@@ -1484,7 +1485,7 @@ msgstr ""
#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22
#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:38
#: lib/block_scout_web/views/block_view.ex:22
-#: lib/block_scout_web/views/wei_helper.ex:77
+#: lib/block_scout_web/views/wei_helper.ex:81
#, elixir-autogen, elixir-format
msgid "Gwei"
msgstr ""
@@ -1600,8 +1601,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6
-#: lib/block_scout_web/views/address_view.ex:374
-#: lib/block_scout_web/views/transaction_view.ex:532
+#: lib/block_scout_web/views/address_view.ex:376
+#: lib/block_scout_web/views/transaction_view.ex:535
#, elixir-autogen, elixir-format
msgid "Internal Transactions"
msgstr ""
@@ -1613,7 +1614,7 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:16
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:19
-#: lib/block_scout_web/views/tokens/overview_view.ex:42
+#: lib/block_scout_web/views/tokens/overview_view.ex:43
#, elixir-autogen, elixir-format
msgid "Inventory"
msgstr ""
@@ -1717,8 +1718,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_logs/index.html.eex:10
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8
-#: lib/block_scout_web/views/address_view.ex:385
-#: lib/block_scout_web/views/transaction_view.ex:533
+#: lib/block_scout_web/views/address_view.ex:387
+#: lib/block_scout_web/views/transaction_view.ex:536
#, elixir-autogen, elixir-format
msgid "Logs"
msgstr ""
@@ -1731,12 +1732,12 @@ msgstr ""
#: lib/block_scout_web/templates/chain/show.html.eex:53
#: lib/block_scout_web/templates/layout/app.html.eex:50
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:85
-#: lib/block_scout_web/views/address_view.ex:145
+#: lib/block_scout_web/views/address_view.ex:147
#, elixir-autogen, elixir-format
msgid "Market Cap"
msgstr ""
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:78
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:84
#, elixir-autogen, elixir-format
msgid "Market cap"
msgstr ""
@@ -1751,7 +1752,7 @@ msgstr ""
msgid "Max Priority Fee per Gas"
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:325
+#: lib/block_scout_web/views/transaction_view.ex:327
#, elixir-autogen, elixir-format
msgid "Max of"
msgstr ""
@@ -1996,7 +1997,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:40
#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:32
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:69
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:75
#, elixir-autogen, elixir-format
msgid "Optimization"
msgstr ""
@@ -2063,8 +2064,8 @@ msgid "Parent Hash"
msgstr ""
#: lib/block_scout_web/templates/layout/_topnav.html.eex:63
-#: lib/block_scout_web/views/transaction_view.ex:368
-#: lib/block_scout_web/views/transaction_view.ex:406
+#: lib/block_scout_web/views/transaction_view.ex:370
+#: lib/block_scout_web/views/transaction_view.ex:409
#, elixir-autogen, elixir-format
msgid "Pending"
msgstr ""
@@ -2081,7 +2082,7 @@ msgid "Play"
msgstr ""
#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:22
-#: lib/block_scout_web/templates/layout/app.html.eex:97
+#: lib/block_scout_web/templates/layout/app.html.eex:100
#, elixir-autogen, elixir-format
msgid "Please confirm your email address to use the My Account feature."
msgstr ""
@@ -2200,22 +2201,22 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24
#: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:1
-#: lib/block_scout_web/views/transaction_view.ex:534
+#: lib/block_scout_web/views/transaction_view.ex:537
#, elixir-autogen, elixir-format
msgid "Raw Trace"
msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:89
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27
-#: lib/block_scout_web/views/address_view.ex:379
-#: lib/block_scout_web/views/tokens/overview_view.ex:41
+#: lib/block_scout_web/views/address_view.ex:381
+#: lib/block_scout_web/views/tokens/overview_view.ex:42
#, elixir-autogen, elixir-format
msgid "Read Contract"
msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:96
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41
-#: lib/block_scout_web/views/address_view.ex:380
+#: lib/block_scout_web/views/address_view.ex:382
#, elixir-autogen, elixir-format
msgid "Read Proxy"
msgstr ""
@@ -2261,7 +2262,7 @@ msgstr ""
msgid "Request to edit a public tag/label"
msgstr ""
-#: lib/block_scout_web/templates/layout/app.html.eex:97
+#: lib/block_scout_web/templates/layout/app.html.eex:100
#, elixir-autogen, elixir-format
msgid "Resend verification email"
msgstr ""
@@ -2468,9 +2469,9 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/index.html.eex:25
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:13
#: lib/block_scout_web/templates/transaction_log/index.html.eex:15
-#: lib/block_scout_web/templates/transaction_state/index.html.eex:8
+#: lib/block_scout_web/templates/transaction_state/index.html.eex:13
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:14
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:45
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:51
#: lib/block_scout_web/templates/withdrawal/index.html.eex:14
#, elixir-autogen, elixir-format
msgid "Something went wrong, click to reload."
@@ -2513,7 +2514,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:29
#: lib/block_scout_web/templates/transaction_state/index.html.eex:6
-#: lib/block_scout_web/views/transaction_view.ex:535
+#: lib/block_scout_web/views/transaction_view.ex:538
#, elixir-autogen, elixir-format
msgid "State changes"
msgstr ""
@@ -2539,7 +2540,7 @@ msgid "Submit an Issue"
msgstr ""
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8
-#: lib/block_scout_web/views/transaction_view.ex:370
+#: lib/block_scout_web/views/transaction_view.ex:372
#, elixir-autogen, elixir-format
msgid "Success"
msgstr ""
@@ -2581,7 +2582,7 @@ msgstr ""
msgid "The block height of a particular block is defined as the number of blocks preceding it in the blockchain."
msgstr ""
-#: lib/block_scout_web/templates/transaction_state/index.html.eex:13
+#: lib/block_scout_web/templates/transaction_state/index.html.eex:18
#, elixir-autogen, elixir-format
msgid "The changes from this transaction have not yet happened since the transaction is still pending."
msgstr ""
@@ -2642,11 +2643,6 @@ msgstr ""
msgid "The total gas amount used in the block and its percentage of gas filled in the block."
msgstr ""
-#: lib/block_scout_web/templates/withdrawal/index.html.eex:11
-#, elixir-autogen, elixir-format
-msgid "There are %{withdrawals_count} withdrawals total that withdrawn %{withdrawals_sum}"
-msgstr ""
-
#: lib/block_scout_web/templates/address_validation/index.html.eex:16
#, elixir-autogen, elixir-format
msgid "There are no blocks validated by this address."
@@ -2729,7 +2725,7 @@ msgstr ""
msgid "There are no transfers for this Token."
msgstr ""
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:93
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:99
#, elixir-autogen, elixir-format
msgid "There are no verified contracts."
msgstr ""
@@ -2800,7 +2796,7 @@ msgstr ""
msgid "This page is no longer explorable! If you are lost, use the search bar to find what you are looking for."
msgstr ""
-#: lib/block_scout_web/templates/transaction_state/index.html.eex:17
+#: lib/block_scout_web/templates/transaction_state/index.html.eex:22
#, elixir-autogen, elixir-format
msgid "This transaction hasn't changed state."
msgstr ""
@@ -2853,13 +2849,13 @@ msgid "Token"
msgstr ""
#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3
-#: lib/block_scout_web/views/transaction_view.ex:468
+#: lib/block_scout_web/views/transaction_view.ex:471
#, elixir-autogen, elixir-format
msgid "Token Burning"
msgstr ""
#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7
-#: lib/block_scout_web/views/transaction_view.ex:469
+#: lib/block_scout_web/views/transaction_view.ex:472
#, elixir-autogen, elixir-format
msgid "Token Creation"
msgstr ""
@@ -2874,7 +2870,7 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16
#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:11
-#: lib/block_scout_web/views/tokens/overview_view.ex:40
+#: lib/block_scout_web/views/tokens/overview_view.ex:41
#, elixir-autogen, elixir-format
msgid "Token Holders"
msgstr ""
@@ -2887,14 +2883,14 @@ msgid "Token ID"
msgstr ""
#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5
-#: lib/block_scout_web/views/transaction_view.ex:467
+#: lib/block_scout_web/views/transaction_view.ex:470
#, elixir-autogen, elixir-format
msgid "Token Minting"
msgstr ""
#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:9
#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:11
-#: lib/block_scout_web/views/transaction_view.ex:470
+#: lib/block_scout_web/views/transaction_view.ex:473
#, elixir-autogen, elixir-format
msgid "Token Transfer"
msgstr ""
@@ -2907,10 +2903,10 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:15
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7
-#: lib/block_scout_web/views/address_view.ex:376
+#: lib/block_scout_web/views/address_view.ex:378
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:114
-#: lib/block_scout_web/views/tokens/overview_view.ex:39
-#: lib/block_scout_web/views/transaction_view.ex:531
+#: lib/block_scout_web/views/tokens/overview_view.ex:40
+#: lib/block_scout_web/views/transaction_view.ex:534
#, elixir-autogen, elixir-format
msgid "Token Transfers"
msgstr ""
@@ -2931,7 +2927,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13
#: lib/block_scout_web/templates/layout/_topnav.html.eex:84
#: lib/block_scout_web/templates/tokens/index.html.eex:10
-#: lib/block_scout_web/views/address_view.ex:373
+#: lib/block_scout_web/views/address_view.ex:375
#, elixir-autogen, elixir-format
msgid "Tokens"
msgstr ""
@@ -2966,7 +2962,7 @@ msgstr ""
msgid "Topic"
msgstr ""
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:71
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:68
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:91
#, elixir-autogen, elixir-format
msgid "Topics"
@@ -3026,7 +3022,7 @@ msgstr ""
#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:11
#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:23
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:19
-#: lib/block_scout_web/views/transaction_view.ex:480
+#: lib/block_scout_web/views/transaction_view.ex:483
#, elixir-autogen, elixir-format
msgid "Transaction"
msgstr ""
@@ -3095,6 +3091,7 @@ msgid "Transaction type, introduced in EIP-2718."
msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:7
+#: lib/block_scout_web/templates/address/_tile.html.eex:31
#: lib/block_scout_web/templates/address/overview.html.eex:186
#: lib/block_scout_web/templates/address/overview.html.eex:192
#: lib/block_scout_web/templates/address/overview.html.eex:200
@@ -3103,7 +3100,7 @@ msgstr ""
#: lib/block_scout_web/templates/block/overview.html.eex:80
#: lib/block_scout_web/templates/chain/show.html.eex:214
#: lib/block_scout_web/templates/layout/_topnav.html.eex:49
-#: lib/block_scout_web/views/address_view.ex:375
+#: lib/block_scout_web/views/address_view.ex:377
#, elixir-autogen, elixir-format
msgid "Transactions"
msgstr ""
@@ -3113,11 +3110,6 @@ msgstr ""
msgid "Transactions and address of creation."
msgstr ""
-#: lib/block_scout_web/templates/address/_tile.html.eex:31
-#, elixir-autogen, elixir-format
-msgid "Transactions sent"
-msgstr ""
-
#: lib/block_scout_web/templates/address/overview.html.eex:213
#: lib/block_scout_web/templates/address/overview.html.eex:219
#: lib/block_scout_web/templates/address/overview.html.eex:227
@@ -3148,7 +3140,7 @@ msgstr ""
msgid "Tx/day"
msgstr ""
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:60
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:66
#, elixir-autogen, elixir-format
msgid "Txns"
msgstr ""
@@ -3185,7 +3177,7 @@ msgstr ""
msgid "Uncles"
msgstr ""
-#: lib/block_scout_web/views/transaction_view.ex:361
+#: lib/block_scout_web/views/transaction_view.ex:363
#, elixir-autogen, elixir-format
msgid "Unconfirmed"
msgstr ""
@@ -3265,7 +3257,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:17
#: lib/block_scout_web/templates/address_write_contract/index.html.eex:15
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:75
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:81
#, elixir-autogen, elixir-format
msgid "Verified"
msgstr ""
@@ -3322,7 +3314,7 @@ msgid "Verify the contract "
msgstr ""
#: lib/block_scout_web/templates/layout/_footer.html.eex:93
-#: lib/block_scout_web/templates/verified_contracts/index.html.eex:66
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:72
#, elixir-autogen, elixir-format
msgid "Version"
msgstr ""
@@ -3452,7 +3444,7 @@ msgstr ""
msgid "We recommend using flattened code. This is necessary if your code utilizes a library or inherits dependencies. Use the"
msgstr ""
-#: lib/block_scout_web/views/wei_helper.ex:76
+#: lib/block_scout_web/views/wei_helper.ex:80
#, elixir-autogen, elixir-format
msgid "Wei"
msgstr ""
@@ -3473,14 +3465,14 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:103
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34
-#: lib/block_scout_web/views/address_view.ex:381
+#: lib/block_scout_web/views/address_view.ex:383
#, elixir-autogen, elixir-format
msgid "Write Contract"
msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:110
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48
-#: lib/block_scout_web/views/address_view.ex:382
+#: lib/block_scout_web/views/address_view.ex:384
#, elixir-autogen, elixir-format
msgid "Write Proxy"
msgstr ""
@@ -3549,16 +3541,6 @@ msgstr ""
msgid "balance of the address"
msgstr ""
-#: lib/block_scout_web/templates/transaction/overview.html.eex:437
-#, elixir-autogen, elixir-format
-msgid "burned for this transaction. Equals Block Base Fee per Gas * Gas Used."
-msgstr ""
-
-#: lib/block_scout_web/templates/block/overview.html.eex:215
-#, elixir-autogen, elixir-format
-msgid "burned from transactions included in the block (Base fee (per unit of gas) * Gas Used)."
-msgstr ""
-
#: lib/block_scout_web/templates/address_contract/index.html.eex:28
#, elixir-autogen, elixir-format
msgid "button"
@@ -3589,7 +3571,7 @@ msgstr ""
msgid "fallback"
msgstr ""
-#: lib/block_scout_web/views/address_contract_view.ex:25
+#: lib/block_scout_web/views/address_contract_view.ex:30
#, elixir-autogen, elixir-format
msgid "false"
msgstr ""
@@ -3616,7 +3598,7 @@ msgstr ""
msgid "of"
msgstr ""
-#: lib/block_scout_web/templates/layout/app.html.eex:97
+#: lib/block_scout_web/templates/layout/app.html.eex:100
#, elixir-autogen, elixir-format
msgid "on sign up. Didn’t receive?"
msgstr ""
@@ -3645,7 +3627,7 @@ msgstr ""
msgid "string"
msgstr ""
-#: lib/block_scout_web/views/address_contract_view.ex:24
+#: lib/block_scout_web/views/address_contract_view.ex:29
#, elixir-autogen, elixir-format
msgid "true"
msgstr ""
@@ -3664,3 +3646,39 @@ msgstr ""
#, elixir-autogen, elixir-format, fuzzy
msgid "New Smart Contract Verification via metadata JSON"
msgstr ""
+
+#: lib/block_scout_web/templates/withdrawal/index.html.eex:11
+#, elixir-autogen, elixir-format, fuzzy
+msgid "%{withdrawals_count} withdrawals processed and %{withdrawals_sum} withdrawn."
+msgstr ""
+
+#: lib/block_scout_web/templates/csv_export/index.html.eex:14
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Export"
+msgstr ""
+
+#: lib/block_scout_web/templates/csv_export/index.html.eex:14
+#, elixir-autogen, elixir-format, fuzzy
+msgid "for address"
+msgstr ""
+
+#: lib/block_scout_web/templates/csv_export/index.html.eex:17
+#, elixir-autogen, elixir-format
+msgid "to CSV file"
+msgstr ""
+
+#: lib/block_scout_web/templates/verified_contracts/index.html.eex:38
+#: lib/block_scout_web/views/verified_contracts_view.ex:12
+#, elixir-autogen, elixir-format
+msgid "Yul"
+msgstr ""
+
+#: lib/block_scout_web/templates/transaction/overview.html.eex:437
+#, elixir-autogen, elixir-format, fuzzy
+msgid "burnt for this transaction. Equals Block Base Fee per Gas * Gas Used."
+msgstr ""
+
+#: lib/block_scout_web/templates/block/overview.html.eex:215
+#, elixir-autogen, elixir-format, fuzzy
+msgid "burnt from transactions included in the block (Base fee (per unit of gas) * Gas Used)."
+msgstr ""
diff --git a/apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs b/apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs
index bac3c7271832..34f26785694d 100644
--- a/apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs
@@ -27,6 +27,7 @@ defmodule BlockScoutWeb.ExchangeRateChannelTest do
id: "test",
last_updated: DateTime.utc_now(),
market_cap_usd: Decimal.new("1000000.0"),
+ tvl_usd: Decimal.new("2000000.0"),
name: "test",
symbol: Explorer.coin(),
usd_value: Decimal.new("2.5"),
diff --git a/apps/block_scout_web/test/block_scout_web/channels/transaction_channel_test.exs b/apps/block_scout_web/test/block_scout_web/channels/transaction_channel_test.exs
index 75005600280c..ea47cc9f7f02 100644
--- a/apps/block_scout_web/test/block_scout_web/channels/transaction_channel_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/channels/transaction_channel_test.exs
@@ -1,7 +1,7 @@
defmodule BlockScoutWeb.TransactionChannelTest do
use BlockScoutWeb.ChannelCase
- alias Explorer.Chain.{Hash, Import, Transaction}
+ alias Explorer.Chain.Hash
alias BlockScoutWeb.Notifier
test "subscribed user is notified of new_transaction topic" do
diff --git a/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs b/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs
index 059fb4fa3855..6d7aad632f90 100644
--- a/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs
@@ -3,10 +3,18 @@ defmodule BlockScoutWeb.WebsocketV2Test do
alias BlockScoutWeb.Notifier
alias Explorer.Chain.Events.Subscriber
- alias Explorer.Chain.{Address, Import, InternalTransaction, Log, Token, TokenTransfer, Transaction}
+ alias Explorer.Chain.{Address, Import, Token, TokenTransfer, Transaction}
alias Explorer.Repo
+ @first_topic_hex_string "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
+ @second_topic_hex_string "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"
+ @third_topic_hex_string "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d"
+
describe "websocket v2" do
+ {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(@first_topic_hex_string)
+ {:ok, second_topic} = Explorer.Chain.Hash.Full.cast(@second_topic_hex_string)
+ {:ok, third_topic} = Explorer.Chain.Hash.Full.cast(@third_topic_hex_string)
+
@import_data %{
blocks: %{
params: [
@@ -34,37 +42,34 @@ defmodule BlockScoutWeb.WebsocketV2Test do
block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000",
- first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
- second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
- third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d",
+ first_topic: first_topic,
+ second_topic: second_topic,
+ third_topic: third_topic,
fourth_topic: nil,
index: 0,
- transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
- type: "mined"
+ transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5"
},
%{
block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000",
- first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
- second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
- third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d",
+ first_topic: first_topic,
+ second_topic: second_topic,
+ third_topic: third_topic,
fourth_topic: nil,
index: 1,
- transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
- type: "mined"
+ transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5"
},
%{
block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000",
- first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
- second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
- third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d",
+ first_topic: first_topic,
+ second_topic: second_topic,
+ third_topic: third_topic,
fourth_topic: nil,
index: 2,
- transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
- type: "mined"
+ transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5"
}
],
timeout: 5
@@ -74,6 +79,7 @@ defmodule BlockScoutWeb.WebsocketV2Test do
%{
block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
block_number: 37,
+ block_timestamp: Timex.parse!("2017-12-15T21:06:30.000000Z", "{ISO:Extended:Z}"),
cumulative_gas_used: 50450,
from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
gas: 4_700_000,
@@ -96,6 +102,7 @@ defmodule BlockScoutWeb.WebsocketV2Test do
%{
block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
block_number: 37,
+ block_timestamp: Timex.parse!("2017-12-15T21:06:30.000000Z", "{ISO:Extended:Z}"),
cumulative_gas_used: 50450,
from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
gas: 4_700_000,
@@ -375,6 +382,11 @@ defmodule BlockScoutWeb.WebsocketV2Test do
assert Address.checksum(transaction.to_address_hash) == json["to"]["hash"]
end
+ # with the current implementation no transfers should come with list in totals
+ defp check_total(%Token{type: nft}, json, _token_transfer) when nft in ["ERC-721", "ERC-1155"] and is_list(json) do
+ false
+ end
+
defp check_total(%Token{type: nft}, json, token_transfer) when nft in ["ERC-1155"] do
json["token_id"] in Enum.map(token_transfer.token_ids, fn x -> to_string(x) end) and
json["value"] == to_string(token_transfer.amount)
@@ -384,10 +396,5 @@ defmodule BlockScoutWeb.WebsocketV2Test do
json["token_id"] in Enum.map(token_transfer.token_ids, fn x -> to_string(x) end)
end
- # with the current implementation no transfers should come with list in totals
- defp check_total(%Token{type: nft}, json, _token_transfer) when nft in ["ERC-721", "ERC-1155"] and is_list(json) do
- false
- end
-
defp check_total(_, _, _), do: true
end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs
index cc7770b71c2a..cb8359f003f5 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs
@@ -1,7 +1,14 @@
defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do
use BlockScoutWeb.ConnCase
+ alias Explorer.Account.{
+ TagAddress,
+ TagTransaction,
+ WatchlistAddress
+ }
+
alias Explorer.Chain.Address
+ alias Explorer.Repo
alias BlockScoutWeb.Models.UserFromAuth
setup %{conn: conn} do
@@ -47,6 +54,50 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do
assert tag_address_response["id"]
end
+ test "can't insert private address tags more than limit", %{conn: conn, user: user} do
+ old_env = Application.get_env(:explorer, Explorer.Account)
+
+ new_env =
+ old_env
+ |> Keyword.replace(:private_tags_limit, 5)
+ |> Keyword.replace(:watchlist_addresses_limit, 5)
+
+ Application.put_env(:explorer, Explorer.Account, new_env)
+
+ for _ <- 0..3 do
+ build(:tag_address_db, user: user) |> Repo.account_repo().insert()
+ end
+
+ assert conn
+ |> post("/api/account/v1/user/tags/address", build(:tag_address))
+ |> json_response(200)
+
+ assert conn
+ |> post("/api/account/v1/user/tags/address", build(:tag_address))
+ |> json_response(422)
+
+ Application.put_env(:explorer, Explorer.Account, old_env)
+ end
+
+ test "check address tags pagination", %{conn: conn, user: user} do
+ tags_address =
+ for _ <- 0..50 do
+ build(:tag_address_db, user: user) |> Repo.account_repo().insert!()
+ end
+
+ assert response =
+ conn
+ |> get("/api/account/v2/user/tags/address")
+ |> json_response(200)
+
+ response_1 =
+ conn
+ |> get("/api/account/v2/user/tags/address", response["next_page_params"])
+ |> json_response(200)
+
+ check_paginated_response(response, response_1, tags_address)
+ end
+
test "edit private address tag", %{conn: conn} do
address_tag = build(:tag_address)
@@ -105,7 +156,8 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do
"name" => nil,
"private_tags" => [],
"public_tags" => [],
- "watchlist_names" => []
+ "watchlist_names" => [],
+ "ens_domain_name" => nil
}
}}
end)
@@ -156,7 +208,8 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do
"name" => nil,
"private_tags" => [],
"public_tags" => [],
- "watchlist_names" => []
+ "watchlist_names" => [],
+ "ens_domain_name" => nil
}
}}
end)
@@ -235,6 +288,50 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do
assert tag_transaction_response["id"]
end
+ test "can't insert private transaction tags more than limit", %{conn: conn, user: user} do
+ old_env = Application.get_env(:explorer, Explorer.Account)
+
+ new_env =
+ old_env
+ |> Keyword.replace(:private_tags_limit, 5)
+ |> Keyword.replace(:watchlist_addresses_limit, 5)
+
+ Application.put_env(:explorer, Explorer.Account, new_env)
+
+ for _ <- 0..3 do
+ build(:tag_transaction_db, user: user) |> Repo.account_repo().insert()
+ end
+
+ assert conn
+ |> post("/api/account/v1/user/tags/transaction", build(:tag_transaction))
+ |> json_response(200)
+
+ assert conn
+ |> post("/api/account/v1/user/tags/transaction", build(:tag_transaction))
+ |> json_response(422)
+
+ Application.put_env(:explorer, Explorer.Account, old_env)
+ end
+
+ test "check transaction tags pagination", %{conn: conn, user: user} do
+ tags_address =
+ for _ <- 0..50 do
+ build(:tag_transaction_db, user: user) |> Repo.account_repo().insert!()
+ end
+
+ assert response =
+ conn
+ |> get("/api/account/v2/user/tags/transaction")
+ |> json_response(200)
+
+ response_1 =
+ conn
+ |> get("/api/account/v2/user/tags/transaction", response["next_page_params"])
+ |> json_response(200)
+
+ check_paginated_response(response, response_1, tags_address)
+ end
+
test "edit private transaction tag", %{conn: conn} do
tx_tag = build(:tag_transaction)
@@ -421,6 +518,50 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do
assert get_watchlist_address_response_1_1["id"] == post_watchlist_address_response_1["id"]
end
+ test "can't insert watchlist addresses more than limit", %{conn: conn, user: user} do
+ old_env = Application.get_env(:explorer, Explorer.Account)
+
+ new_env =
+ old_env
+ |> Keyword.replace(:private_tags_limit, 5)
+ |> Keyword.replace(:watchlist_addresses_limit, 5)
+
+ Application.put_env(:explorer, Explorer.Account, new_env)
+
+ for _ <- 0..3 do
+ build(:watchlist_address_db, wl_id: user.watchlist_id) |> Repo.account_repo().insert()
+ end
+
+ assert conn
+ |> post("/api/account/v1/user/watchlist", build(:watchlist_address))
+ |> json_response(200)
+
+ assert conn
+ |> post("/api/account/v1/user/watchlist", build(:watchlist_address))
+ |> json_response(422)
+
+ Application.put_env(:explorer, Explorer.Account, old_env)
+ end
+
+ test "check watchlist tags pagination", %{conn: conn, user: user} do
+ tags_address =
+ for _ <- 0..50 do
+ build(:watchlist_address_db, wl_id: user.watchlist_id) |> Repo.account_repo().insert!()
+ end
+
+ assert response =
+ conn
+ |> get("/api/account/v2/user/watchlist")
+ |> json_response(200)
+
+ response_1 =
+ conn
+ |> get("/api/account/v2/user/watchlist", response["next_page_params"])
+ |> json_response(200)
+
+ check_paginated_response(response, response_1, tags_address)
+ end
+
test "delete watchlist address", %{conn: conn} do
watchlist_address_map = build(:watchlist_address)
@@ -573,6 +714,111 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do
|> json_response(422) == %{"errors" => %{"watchlist_id" => ["Address already added to the watch list"]}}
end
+ test "watchlist address returns with token balances info", %{conn: conn} do
+ watchlist_address_map = build(:watchlist_address)
+
+ conn
+ |> post(
+ "/api/account/v1/user/watchlist",
+ watchlist_address_map
+ )
+ |> json_response(200)
+
+ watchlist_address_map_1 = build(:watchlist_address)
+
+ conn
+ |> post(
+ "/api/account/v1/user/watchlist",
+ watchlist_address_map_1
+ )
+ |> json_response(200)
+
+ values =
+ for _i <- 0..149 do
+ ctb =
+ insert(:address_current_token_balance_with_token_id,
+ address: Repo.get_by(Address, hash: watchlist_address_map["address_hash"])
+ )
+ |> Repo.preload([:token])
+
+ Decimal.div(
+ Decimal.mult(ctb.value, ctb.token.fiat_value),
+ Decimal.new(10 ** Decimal.to_integer(ctb.token.decimals))
+ )
+ end
+
+ values_1 =
+ for _i <- 0..200 do
+ ctb =
+ insert(:address_current_token_balance_with_token_id,
+ address: Repo.get_by(Address, hash: watchlist_address_map_1["address_hash"])
+ )
+ |> Repo.preload([:token])
+
+ Decimal.div(
+ Decimal.mult(ctb.value, ctb.token.fiat_value),
+ Decimal.new(10 ** Decimal.to_integer(ctb.token.decimals))
+ )
+ end
+ |> Enum.sort(fn x1, x2 -> Decimal.compare(x1, x2) in [:gt, :eq] end)
+ |> Enum.take(150)
+
+ [wa2, wa1] = conn |> get("/api/account/v1/user/watchlist") |> json_response(200)
+
+ assert wa1["tokens_fiat_value"] |> Decimal.new() |> Decimal.round(13) ==
+ values |> Enum.reduce(Decimal.new(0), fn x, acc -> Decimal.add(x, acc) end) |> Decimal.round(13)
+
+ assert wa1["tokens_count"] == 150
+ assert wa1["tokens_overflow"] == false
+
+ assert wa2["tokens_fiat_value"] |> Decimal.new() |> Decimal.round(13) ==
+ values_1 |> Enum.reduce(Decimal.new(0), fn x, acc -> Decimal.add(x, acc) end) |> Decimal.round(13)
+
+ assert wa2["tokens_count"] == 150
+ assert wa2["tokens_overflow"] == true
+ end
+
+ test "watchlist address returns with token balances info + handle nil fiat values", %{conn: conn} do
+ watchlist_address_map = build(:watchlist_address)
+
+ conn
+ |> post(
+ "/api/account/v1/user/watchlist",
+ watchlist_address_map
+ )
+ |> json_response(200)
+
+ values =
+ for _i <- 0..148 do
+ ctb =
+ insert(:address_current_token_balance_with_token_id,
+ address: Repo.get_by(Address, hash: watchlist_address_map["address_hash"])
+ )
+ |> Repo.preload([:token])
+
+ Decimal.div(
+ Decimal.mult(ctb.value, ctb.token.fiat_value),
+ Decimal.new(10 ** Decimal.to_integer(ctb.token.decimals))
+ )
+ end
+
+ token = insert(:token, fiat_value: nil)
+
+ insert(:address_current_token_balance_with_token_id,
+ address: Repo.get_by(Address, hash: watchlist_address_map["address_hash"]),
+ token: token,
+ token_contract_address_hash: token.contract_address_hash
+ )
+
+ [wa1] = conn |> get("/api/account/v1/user/watchlist") |> json_response(200)
+
+ assert wa1["tokens_fiat_value"] |> Decimal.new() |> Decimal.round(13) ==
+ values |> Enum.reduce(Decimal.new(0), fn x, acc -> Decimal.add(x, acc) end) |> Decimal.round(13)
+
+ assert wa1["tokens_count"] == 150
+ assert wa1["tokens_overflow"] == false
+ end
+
test "post api key", %{conn: conn} do
post_api_key_response =
conn
@@ -945,4 +1191,52 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do
{:ok, time, _} = DateTime.from_iso8601(request["submission_date"])
%{request | "submission_date" => Calendar.strftime(time, "%b %d, %Y")}
end
+
+ defp compare_item(%TagAddress{} = tag_address, json) do
+ assert json["address_hash"] == to_string(tag_address.address_hash)
+ assert json["name"] == tag_address.name
+ assert json["id"] == tag_address.id
+ assert json["address"]["hash"] == Address.checksum(tag_address.address_hash)
+ end
+
+ defp compare_item(%TagTransaction{} = tag_transaction, json) do
+ assert json["transaction_hash"] == to_string(tag_transaction.tx_hash)
+ assert json["name"] == tag_transaction.name
+ assert json["id"] == tag_transaction.id
+ end
+
+ defp compare_item(%WatchlistAddress{} = watchlist, json) do
+ notification_settings = %{
+ "native" => %{
+ "incoming" => watchlist.watch_coin_input,
+ "outcoming" => watchlist.watch_coin_output
+ },
+ "ERC-20" => %{
+ "incoming" => watchlist.watch_erc_20_input,
+ "outcoming" => watchlist.watch_erc_20_output
+ },
+ "ERC-721" => %{
+ "incoming" => watchlist.watch_erc_721_input,
+ "outcoming" => watchlist.watch_erc_721_output
+ }
+ }
+
+ assert json["address_hash"] == to_string(watchlist.address_hash)
+ assert json["name"] == watchlist.name
+ assert json["id"] == watchlist.id
+ assert json["address"]["hash"] == Address.checksum(watchlist.address_hash)
+ assert json["notification_methods"]["email"] == watchlist.notify_email
+ assert json["notification_settings"] == notification_settings
+ end
+
+ defp check_paginated_response(first_page_resp, second_page_resp, list) do
+ assert Enum.count(first_page_resp["items"]) == 50
+ assert first_page_resp["next_page_params"] != nil
+ compare_item(Enum.at(list, 50), Enum.at(first_page_resp["items"], 0))
+ compare_item(Enum.at(list, 1), Enum.at(first_page_resp["items"], 49))
+
+ assert Enum.count(second_page_resp["items"]) == 1
+ assert second_page_resp["next_page_params"] == nil
+ compare_item(Enum.at(list, 0), Enum.at(second_page_resp["items"], 0))
+ end
end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs
index 5301e0b1c54b..86a15c8d43fb 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs
@@ -538,7 +538,8 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
from_address: address,
index: index,
block_hash: transaction.block_hash,
- block_index: index
+ block_index: index,
+ block_number: transaction.block_number
)
end)
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs
index 2a05b0c65980..37f279b32562 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs
@@ -7,6 +7,8 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do
import Mox
+ setup :verify_on_exit!
+
describe "GET index/3" do
setup :set_mox_global
@@ -51,7 +53,7 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do
insert(:smart_contract, address_hash: contract_address.hash, contract_code_md5: "123")
- get_eip1967_implementation()
+ request_zero_implementations()
conn =
get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
@@ -82,7 +84,7 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do
end
end
- def get_eip1967_implementation do
+ def request_zero_implementations do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
@@ -120,5 +122,17 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
end
end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs
index 77d5a8060f9e..14b42c6ae55f 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs
@@ -7,6 +7,8 @@ defmodule BlockScoutWeb.AddressReadProxyControllerTest do
import Mox
+ setup :verify_on_exit!
+
describe "GET index/3" do
setup :set_mox_global
@@ -51,7 +53,7 @@ defmodule BlockScoutWeb.AddressReadProxyControllerTest do
insert(:smart_contract, address_hash: contract_address.hash, contract_code_md5: "123")
- get_eip1967_implementation()
+ request_zero_implementations()
conn = get(conn, address_read_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
@@ -80,7 +82,7 @@ defmodule BlockScoutWeb.AddressReadProxyControllerTest do
end
end
- def get_eip1967_implementation do
+ def request_zero_implementations do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
@@ -118,5 +120,17 @@ defmodule BlockScoutWeb.AddressReadProxyControllerTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
end
end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs
index 3e1434750870..ad86587530fc 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs
@@ -176,7 +176,7 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
conn =
- get(conn, "/token-transfers-csv", %{
+ get(conn, "/api/v1/token-transfers-csv", %{
"address_id" => Address.checksum(address.hash),
"from_period" => from_period,
"to_period" => to_period
@@ -203,7 +203,7 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
conn =
- get(conn, "/token-transfers-csv", %{
+ get(conn, "/api/v1/token-transfers-csv", %{
"address_id" => Address.checksum(address.hash),
"from_period" => from_period,
"to_period" => to_period,
@@ -231,7 +231,7 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
conn =
- get(conn, "/token-transfers-csv", %{
+ get(conn, "/api/v1/token-transfers-csv", %{
"address_id" => Address.checksum(address.hash),
"from_period" => from_period,
"to_period" => to_period
@@ -260,7 +260,7 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
conn =
- get(conn, "/token-transfers-csv", %{
+ get(conn, "/api/v1/token-transfers-csv", %{
"address_id" => Address.checksum(address.hash),
"from_period" => from_period,
"to_period" => to_period,
@@ -290,7 +290,7 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
conn =
- get(conn, "/transactions-csv", %{
+ get(conn, "/api/v1/transactions-csv", %{
"address_id" => Address.checksum(address.hash),
"from_period" => from_period,
"to_period" => to_period,
@@ -357,7 +357,7 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
conn =
- get(conn, "/internal-transactions-csv", %{
+ get(conn, "/api/v1/internal-transactions-csv", %{
"address_id" => Address.checksum(address.hash),
"from_period" => from_period,
"to_period" => to_period,
@@ -418,7 +418,7 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
conn =
- get(conn, "/logs-csv", %{
+ get(conn, "/api/v1/logs-csv", %{
"address_id" => Address.checksum(address.hash),
"from_period" => from_period,
"to_period" => to_period,
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_withdrawal_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_withdrawal_controller_test.exs
index a6df1165ae8c..557c906ac116 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/address_withdrawal_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/address_withdrawal_controller_test.exs
@@ -6,7 +6,7 @@ defmodule BlockScoutWeb.AddressWithdrawalControllerTest do
import BlockScoutWeb.WeiHelper, only: [format_wei_value: 2]
import Mox
- alias Explorer.Chain.{Address, Transaction}
+ alias Explorer.Chain.Address
alias Explorer.ExchangeRates.Token
setup :verify_on_exit!
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs
index b577be9c74e3..beb197b8b592 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs
@@ -9,6 +9,8 @@ defmodule BlockScoutWeb.AddressWriteContractControllerTest do
import Mox
+ setup :verify_on_exit!
+
describe "GET index/3" do
setup :set_mox_global
@@ -53,7 +55,7 @@ defmodule BlockScoutWeb.AddressWriteContractControllerTest do
insert(:smart_contract, address_hash: contract_address.hash, contract_code_md5: "123")
- get_eip1967_implementation()
+ request_zero_implementations()
conn =
get(conn, address_write_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
@@ -84,7 +86,7 @@ defmodule BlockScoutWeb.AddressWriteContractControllerTest do
end
end
- def get_eip1967_implementation do
+ def request_zero_implementations do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
@@ -122,5 +124,17 @@ defmodule BlockScoutWeb.AddressWriteContractControllerTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
end
end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs
index 01b1b931134a..377f8f1a3881 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs
@@ -7,6 +7,8 @@ defmodule BlockScoutWeb.AddressWriteProxyControllerTest do
import Mox
+ setup :verify_on_exit!
+
describe "GET index/3" do
setup :set_mox_global
@@ -51,7 +53,7 @@ defmodule BlockScoutWeb.AddressWriteProxyControllerTest do
insert(:smart_contract, address_hash: contract_address.hash, contract_code_md5: "123")
- get_eip1967_implementation()
+ request_zero_implementations()
conn =
get(conn, address_write_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
@@ -82,7 +84,7 @@ defmodule BlockScoutWeb.AddressWriteProxyControllerTest do
end
end
- def get_eip1967_implementation do
+ def request_zero_implementations do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
@@ -120,5 +122,17 @@ defmodule BlockScoutWeb.AddressWriteProxyControllerTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
end
end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
index ae0b2f3b944e..f872c8928fd9 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
@@ -1246,7 +1246,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
for block <- Enum.concat([blocks1, blocks2, blocks3]) do
2
- |> insert_list(:transaction, from_address: address)
+ |> insert_list(:transaction, from_address: address, block_timestamp: block.timestamp)
|> with_block(block)
end
@@ -1294,7 +1294,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
for block <- Enum.concat([blocks1, blocks2, blocks3]) do
2
- |> insert_list(:transaction, from_address: address)
+ |> insert_list(:transaction, from_address: address, block_timestamp: block.timestamp)
|> with_block(block)
end
@@ -1342,7 +1342,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
for block <- Enum.concat([blocks1, blocks2, blocks3]) do
2
- |> insert_list(:transaction, from_address: address)
+ |> insert_list(:transaction, from_address: address, block_timestamp: block.timestamp)
|> with_block(block)
end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs
index 0cd2b50bd390..89122b8369b4 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs
@@ -1,11 +1,11 @@
defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
use BlockScoutWeb.ConnCase
alias Explorer.Chain.SmartContract
- alias Explorer.Chain
- # alias Explorer.{Chain, Factory}
import Mox
+ setup :verify_on_exit!
+
def prepare_contracts do
insert(:contract_address)
{:ok, dt_1, _} = DateTime.from_iso8601("2022-09-20 10:00:00Z")
@@ -649,7 +649,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
%SmartContract.ExternalLibrary{:address_hash => "0x283539e1b1daf24cdd58a3e934d55062ea663c3f", :name => "Test2"}
]
- {:ok, %SmartContract{} = contract} = Chain.create_smart_contract(valid_attrs, external_libraries)
+ {:ok, %SmartContract{} = contract} = SmartContract.create_smart_contract(valid_attrs, external_libraries)
params = %{
"module" => "contract",
@@ -712,7 +712,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
params = %{
"module" => "contract",
"action" => "verify_via_sourcify",
- "addressHash" => "0x18d89C12e9463Be6343c35C9990361bA4C42AfC2"
+ "addressHash" => "0xf26594F585De4EB0Ae9De865d9053FEe02ac6eF1"
}
response =
@@ -732,14 +732,14 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
_created_contract_address =
insert(
:address,
- hash: "0x18d89C12e9463Be6343c35C9990361bA4C42AfC2",
+ hash: "0xf26594F585De4EB0Ae9De865d9053FEe02ac6eF1",
contract_code: smart_contract_bytecode
)
params = %{
"module" => "contract",
"action" => "verify_via_sourcify",
- "addressHash" => "0x18d89C12e9463Be6343c35C9990361bA4C42AfC2"
+ "addressHash" => "0xf26594F585De4EB0Ae9De865d9053FEe02ac6eF1"
}
get_implementation()
@@ -771,7 +771,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
# |> get("/api", params)
# |> json_response(200)
- # verified_contract = Chain.address_hash_to_smart_contract(contract_address.hash)
+ # verified_contract = SmartContract.address_hash_to_smart_contract(contract_address.hash)
# expected_result = %{
# "Address" => to_string(contract_address.hash),
@@ -844,7 +844,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
# result = response["result"]
- # verified_contract = Chain.address_hash_to_smart_contract(contract_address.hash)
+ # verified_contract = SmartContract.address_hash_to_smart_contract(contract_address.hash)
# assert result["Address"] == to_string(contract_address.hash)
@@ -967,5 +967,17 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
end
end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs
index e80a8bbc7c92..b77a23a39a30 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs
@@ -5,6 +5,12 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
alias Explorer.Repo
alias Indexer.Fetcher.CoinBalanceOnDemand
+ @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65"
+ @first_topic_hex_string_2 "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
+
+ @second_topic_hex_string_1 "0x00000000000000000000000098a9dc37d3650b5b30d6c12789b3881ee0b70c16"
+ @second_topic_hex_string_2 "0x000000000000000000000000e2680fd7cdbb04e9087a647ad4d023ef6c8fb4e2"
+
setup do
mocked_json_rpc_named_arguments = [
transport: EthereumJSONRPC.Mox,
@@ -27,6 +33,11 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
defp params(api_params, params), do: Map.put(api_params, "params", params)
+ defp topic(topic_hex_string) do
+ {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string)
+ topic
+ end
+
describe "eth_get_logs" do
setup do
%{
@@ -76,7 +87,14 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
block = insert(:block, number: 0)
transaction = insert(:transaction, from_address: address) |> with_block(block)
- insert(:log, block: block, address: address, transaction: transaction, data: "0x010101")
+
+ insert(:log,
+ block: block,
+ block_number: block.number,
+ address: address,
+ transaction: transaction,
+ data: "0x010101"
+ )
params = params(api_params, [%{"address" => to_string(address.hash)}])
@@ -94,9 +112,17 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
block = insert(:block, number: 0)
transaction = insert(:transaction, from_address: address) |> with_block(block)
- insert(:log, block: block, address: address, transaction: transaction, data: "0x010101", first_topic: "0x01")
- params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01"]}])
+ insert(:log,
+ block: block,
+ block_number: block.number,
+ address: address,
+ transaction: transaction,
+ data: "0x010101",
+ first_topic: topic(@first_topic_hex_string_1)
+ )
+
+ params = params(api_params, [%{"address" => to_string(address.hash), "topics" => [@first_topic_hex_string_1]}])
assert response =
conn
@@ -112,10 +138,29 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
block = insert(:block, number: 0)
transaction = insert(:transaction, from_address: address) |> with_block(block)
- insert(:log, address: address, block: block, transaction: transaction, data: "0x010101", first_topic: "0x01")
- insert(:log, address: address, block: block, transaction: transaction, data: "0x020202", first_topic: "0x00")
- params = params(api_params, [%{"address" => to_string(address.hash), "topics" => [["0x01", "0x00"]]}])
+ insert(:log,
+ address: address,
+ block: block,
+ block_number: block.number,
+ transaction: transaction,
+ data: "0x010101",
+ first_topic: topic(@first_topic_hex_string_1)
+ )
+
+ insert(:log,
+ address: address,
+ block: block,
+ block_number: block.number,
+ transaction: transaction,
+ data: "0x020202",
+ first_topic: topic(@first_topic_hex_string_2)
+ )
+
+ params =
+ params(api_params, [
+ %{"address" => to_string(address.hash), "topics" => [[@first_topic_hex_string_1, @first_topic_hex_string_2]]}
+ ])
assert response =
conn
@@ -135,9 +180,16 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
|> with_block(block)
inserted_records =
- insert_list(2000, :log, block: block, address: contract_address, transaction: transaction, first_topic: "0x01")
+ insert_list(2000, :log,
+ block: block,
+ block_number: block.number,
+ address: contract_address,
+ transaction: transaction,
+ first_topic: topic(@first_topic_hex_string_1)
+ )
- params = params(api_params, [%{"address" => to_string(contract_address), "topics" => [["0x01"]]}])
+ params =
+ params(api_params, [%{"address" => to_string(contract_address), "topics" => [[@first_topic_hex_string_1]]}])
assert response =
conn
@@ -150,13 +202,16 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
next_page_params = %{
"blockNumber" => Integer.to_string(transaction.block_number, 16),
- "transactionIndex" => transaction.index,
"logIndex" => Integer.to_string(last_log_index, 16)
}
new_params =
params(api_params, [
- %{"paging_options" => next_page_params, "address" => to_string(contract_address), "topics" => [["0x01"]]}
+ %{
+ "paging_options" => next_page_params,
+ "address" => to_string(contract_address),
+ "topics" => [[@first_topic_hex_string_1]]
+ }
])
assert new_response =
@@ -191,14 +246,24 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
address: address,
transaction: transaction,
data: "0x010101",
- first_topic: "0x01",
- second_topic: "0x02",
- block: block
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_1),
+ block: block,
+ block_number: block.number
)
- insert(:log, block: block, address: address, transaction: transaction, data: "0x020202", first_topic: "0x01")
+ insert(:log,
+ block: block,
+ address: address,
+ transaction: transaction,
+ data: "0x020202",
+ first_topic: topic(@first_topic_hex_string_1)
+ )
- params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01", "0x02"]}])
+ params =
+ params(api_params, [
+ %{"address" => to_string(address.hash), "topics" => [@first_topic_hex_string_1, @second_topic_hex_string_1]}
+ ])
assert response =
conn
@@ -220,21 +285,29 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
address: address,
transaction: transaction,
data: "0x010101",
- first_topic: "0x01",
- second_topic: "0x02",
- block: block
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_1),
+ block: block,
+ block_number: block.number
)
insert(:log,
address: address,
transaction: transaction,
data: "0x020202",
- first_topic: "0x01",
- second_topic: "0x03",
- block: block
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_2),
+ block: block,
+ block_number: block.number
)
- params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01", ["0x02", "0x03"]]}])
+ params =
+ params(api_params, [
+ %{
+ "address" => to_string(address.hash),
+ "topics" => [@first_topic_hex_string_1, [@second_topic_hex_string_1, @second_topic_hex_string_2]]
+ }
+ ])
assert response =
conn
@@ -258,13 +331,13 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
transaction3 = insert(:transaction, from_address: address) |> with_block(block3)
transaction4 = insert(:transaction, from_address: address) |> with_block(block4)
- insert(:log, address: address, transaction: transaction1, data: "0x010101")
+ insert(:log, address: address, transaction: transaction1, data: "0x010101", block_number: block1.number)
- insert(:log, address: address, transaction: transaction2, data: "0x020202")
+ insert(:log, address: address, transaction: transaction2, data: "0x020202", block_number: block2.number)
- insert(:log, address: address, transaction: transaction3, data: "0x030303")
+ insert(:log, address: address, transaction: transaction3, data: "0x030303", block_number: block3.number)
- insert(:log, address: address, transaction: transaction4, data: "0x040404")
+ insert(:log, address: address, transaction: transaction4, data: "0x040404", block_number: block4.number)
params = params(api_params, [%{"address" => to_string(address.hash), "fromBlock" => 1, "toBlock" => 2}])
@@ -288,11 +361,11 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
transaction2 = insert(:transaction, from_address: address) |> with_block(block2)
transaction3 = insert(:transaction, from_address: address) |> with_block(block3)
- insert(:log, address: address, transaction: transaction1, data: "0x010101")
+ insert(:log, address: address, transaction: transaction1, data: "0x010101", block_number: block1.number)
- insert(:log, address: address, transaction: transaction2, data: "0x020202")
+ insert(:log, address: address, transaction: transaction2, data: "0x020202", block_number: block2.number)
- insert(:log, address: address, transaction: transaction3, data: "0x030303")
+ insert(:log, address: address, transaction: transaction3, data: "0x030303", block_number: block3.number)
params = params(api_params, [%{"address" => to_string(address.hash), "blockHash" => to_string(block2.hash)}])
@@ -316,11 +389,11 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
transaction2 = insert(:transaction, from_address: address) |> with_block(block2)
transaction3 = insert(:transaction, from_address: address) |> with_block(block3)
- insert(:log, address: address, transaction: transaction1, data: "0x010101")
+ insert(:log, address: address, transaction: transaction1, data: "0x010101", block_number: block1.number)
- insert(:log, address: address, transaction: transaction2, data: "0x020202")
+ insert(:log, address: address, transaction: transaction2, data: "0x020202", block_number: block2.number)
- insert(:log, address: address, transaction: transaction3, data: "0x030303")
+ insert(:log, address: address, transaction: transaction3, data: "0x030303", block_number: block3.number)
params =
params(api_params, [%{"address" => to_string(address.hash), "fromBlock" => "earliest", "toBlock" => "earliest"}])
@@ -345,11 +418,29 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
transaction2 = insert(:transaction, from_address: address) |> with_block(block2)
transaction3 = insert(:transaction, from_address: address) |> with_block(block3)
- insert(:log, block: block1, address: address, transaction: transaction1, data: "0x010101")
+ insert(:log,
+ block: block1,
+ block_number: block1.number,
+ address: address,
+ transaction: transaction1,
+ data: "0x010101"
+ )
- insert(:log, block: block2, address: address, transaction: transaction2, data: "0x020202")
+ insert(:log,
+ block: block2,
+ block_number: block2.number,
+ address: address,
+ transaction: transaction2,
+ data: "0x020202"
+ )
- insert(:log, block: block3, address: address, transaction: transaction3, data: "0x030303")
+ insert(:log,
+ block: block3,
+ block_number: block3.number,
+ address: address,
+ transaction: transaction3,
+ data: "0x030303"
+ )
changeset = Ecto.Changeset.change(block3, %{consensus: false})
@@ -400,9 +491,7 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
test "with a valid address that has a balance", %{conn: conn, api_params: api_params} do
block = insert(:block)
- address = insert(:address)
-
- insert(:fetched_balance, block_number: block.number, address_hash: address.hash, value: 1)
+ address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: block.number)
assert response =
conn
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs
index ec4337eecd84..2691f1e7e825 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs
@@ -4,6 +4,22 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do
alias BlockScoutWeb.API.RPC.LogsController
alias Explorer.Chain.{Log, Transaction}
+ @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65"
+ @first_topic_hex_string_2 "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
+
+ @second_topic_hex_string_1 "0x00000000000000000000000098a9dc37d3650b5b30d6c12789b3881ee0b70c16"
+ @second_topic_hex_string_2 "0x000000000000000000000000e2680fd7cdbb04e9087a647ad4d023ef6c8fb4e2"
+
+ @third_topic_hex_string_1 "0x0000000000000000000000005079fc00f00f30000e0c8c083801cfde000008b6"
+
+ @fourth_topic_hex_string_1 "0x8c9b7729443a4444242342b2ca385a239a5c1d76a88473e1cd2ab0c70dd1b9c7"
+ @fourth_topic_hex_string_2 "0x232b688786cc0d24a11e07563c1bfa129537cec9385dc5b1fb8f86462977239b"
+
+ defp topic(topic_hex_string) do
+ {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string)
+ topic
+ end
+
describe "getLogs" do
test "without fromBlock, toBlock, address, and topic{x}", %{conn: conn} do
params = %{
@@ -280,7 +296,7 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do
|> insert(to_address: contract_address)
|> with_block()
- log = insert(:log, address: contract_address, transaction: transaction)
+ log = insert(:log, address: contract_address, transaction: transaction, block_number: transaction.block_number)
params = %{
"module" => "logs",
@@ -334,8 +350,17 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do
|> insert(to_address: contract_address)
|> with_block(second_block)
- insert(:log, address: contract_address, transaction: transaction_block1)
- insert(:log, address: contract_address, transaction: transaction_block2)
+ insert(:log,
+ address: contract_address,
+ transaction: transaction_block1,
+ block_number: transaction_block1.block_number
+ )
+
+ insert(:log,
+ address: contract_address,
+ transaction: transaction_block2,
+ block_number: transaction_block2.block_number
+ )
params = %{
"module" => "logs",
@@ -378,8 +403,17 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do
|> insert(to_address: contract_address)
|> with_block(second_block)
- insert(:log, address: contract_address, transaction: transaction_block1)
- insert(:log, address: contract_address, transaction: transaction_block2)
+ insert(:log,
+ address: contract_address,
+ transaction: transaction_block1,
+ block_number: transaction_block1.block_number
+ )
+
+ insert(:log,
+ address: contract_address,
+ transaction: transaction_block2,
+ block_number: transaction_block2.block_number
+ )
params = %{
"module" => "logs",
@@ -416,13 +450,13 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do
log1_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some topic"
+ first_topic: topic(@first_topic_hex_string_1)
]
log2_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some other topic"
+ first_topic: topic(@first_topic_hex_string_2)
]
log1 = insert(:log, log1_details)
@@ -474,15 +508,15 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do
log1_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some topic",
- second_topic: "some second topic"
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_1)
]
log2_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some other topic",
- second_topic: "some other second topic"
+ first_topic: topic(@first_topic_hex_string_2),
+ second_topic: topic(@second_topic_hex_string_2)
]
log1 = insert(:log, log1_details)
@@ -523,15 +557,15 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do
log1_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some topic",
- second_topic: "some second topic"
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_1)
]
log2_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some other topic",
- second_topic: "some other second topic"
+ first_topic: topic(@first_topic_hex_string_2),
+ second_topic: topic(@second_topic_hex_string_2)
]
log1 = insert(:log, log1_details)
@@ -571,19 +605,19 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do
log1_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some topic",
- second_topic: "some second topic",
- third_topic: "some third topic",
- fourth_topic: "some fourth topic"
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_1),
+ third_topic: topic(@third_topic_hex_string_1),
+ fourth_topic: topic(@fourth_topic_hex_string_1)
]
log2_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some topic",
- second_topic: "some second topic",
- third_topic: "some third topic",
- fourth_topic: "some other fourth topic"
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_1),
+ third_topic: topic(@third_topic_hex_string_1),
+ fourth_topic: topic(@fourth_topic_hex_string_2)
]
log1 = insert(:log, log1_details)
@@ -773,7 +807,12 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do
third_topic: third_topic,
fourth_topic: fourth_topic
}) do
- [first_topic, second_topic, third_topic, fourth_topic]
+ [
+ first_topic && Explorer.Chain.Hash.to_string(first_topic),
+ second_topic && Explorer.Chain.Hash.to_string(second_topic),
+ third_topic && Explorer.Chain.Hash.to_string(third_topic),
+ fourth_topic && Explorer.Chain.Hash.to_string(fourth_topic)
+ ]
end
defp integer_to_hex(integer), do: "0x" <> String.downcase(Integer.to_string(integer, 16))
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs
index 5722cab5bcba..5731311f11b7 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs
@@ -169,6 +169,7 @@ defmodule BlockScoutWeb.API.RPC.StatsControllerTest do
id: "test",
last_updated: DateTime.utc_now(),
market_cap_usd: Decimal.new("1000000.0"),
+ tvl_usd: Decimal.new("2000000.0"),
name: "test",
symbol: symbol,
usd_value: Decimal.new("1.0"),
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs
index e9e08b6a7127..8e30a3e68046 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs
@@ -5,6 +5,16 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
@moduletag capture_log: true
+ @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65"
+ @second_topic_hex_string_1 "0x00000000000000000000000098a9dc37d3650b5b30d6c12789b3881ee0b70c16"
+
+ setup :verify_on_exit!
+
+ defp topic(topic_hex_string) do
+ {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string)
+ topic
+ end
+
describe "gettxreceiptstatus" do
test "with missing txhash", %{conn: conn} do
params = %{
@@ -412,8 +422,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
insert(:log,
address: address,
transaction: transaction,
- first_topic: "first topic",
- second_topic: "second topic",
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_1),
block: block,
block_number: block.number
)
@@ -489,8 +499,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
insert(:log,
address: address,
transaction: transaction,
- first_topic: "first topic",
- second_topic: "second topic",
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_1),
block: block,
block_number: block.number
)
@@ -518,7 +528,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
%{
"address" => "#{address.hash}",
"data" => "#{log.data}",
- "topics" => ["first topic", "second topic", nil, nil],
+ "topics" => [@first_topic_hex_string_1, @second_topic_hex_string_1, nil, nil],
"index" => "#{log.index}"
}
],
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs
index b414a917bc7c..34829785f55e 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs
@@ -1,8 +1,10 @@
defmodule BlockScoutWeb.API.V2.AddressControllerTest do
use BlockScoutWeb.ConnCase
+ use EthereumJSONRPC.Case, async: false
alias BlockScoutWeb.Models.UserFromAuth
alias Explorer.{Chain, Repo}
+ alias Explorer.Chain.Address.Counters
alias Explorer.Chain.{
Address,
@@ -11,8 +13,10 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
InternalTransaction,
Log,
Token,
+ Token.Instance,
TokenTransfer,
Transaction,
+ Wei,
Withdrawal
}
@@ -20,6 +24,19 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
alias Explorer.Chain.Address.CurrentTokenBalance
import Explorer.Chain, only: [hash_to_lower_case_string: 1]
+ import Mox
+
+ @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65"
+ @instances_amount_in_collection 9
+
+ setup :set_mox_global
+
+ setup :verify_on_exit!
+
+ defp topic(topic_hex_string) do
+ {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string)
+ topic
+ end
describe "/addresses/{address_hash}" do
test "get 404 on non existing address", %{conn: conn} do
@@ -42,7 +59,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
correct_response = %{
"hash" => Address.checksum(address.hash),
"is_contract" => false,
- "is_verified" => false,
+ "is_verified" => nil,
"name" => nil,
"private_tags" => [],
"public_tags" => [],
@@ -67,7 +84,8 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
"has_tokens" => false,
"has_token_transfers" => false,
"watchlist_address_id" => nil,
- "has_beacon_chain_withdrawals" => false
+ "has_beacon_chain_withdrawals" => false,
+ "ens_domain_name" => nil
}
request = get(conn, "/api/v2/addresses/#{Address.checksum(address.hash)}")
@@ -77,6 +95,47 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
assert ^correct_response = json_response(request, 200)
end
+ test "get contract info", %{conn: conn} do
+ smart_contract = insert(:smart_contract)
+
+ tx =
+ insert(:transaction,
+ to_address_hash: nil,
+ to_address: nil,
+ created_contract_address_hash: smart_contract.address_hash,
+ created_contract_address: smart_contract.address
+ )
+
+ insert(:address_name,
+ address: smart_contract.address,
+ primary: true,
+ name: smart_contract.name,
+ address_hash: smart_contract.address_hash
+ )
+
+ name = smart_contract.name
+ from = Address.checksum(tx.from_address_hash)
+ tx_hash = to_string(tx.hash)
+ address_hash = Address.checksum(smart_contract.address_hash)
+
+ get_eip1967_implementation_non_zero_address()
+
+ request = get(conn, "/api/v2/addresses/#{Address.checksum(smart_contract.address_hash)}")
+
+ assert %{
+ "hash" => ^address_hash,
+ "is_contract" => true,
+ "is_verified" => true,
+ "name" => ^name,
+ "private_tags" => [],
+ "public_tags" => [],
+ "watchlist_names" => [],
+ "creator_address_hash" => ^from,
+ "creation_tx_hash" => ^tx_hash,
+ "implementation_address" => "0x0000000000000000000000000000000000000001"
+ } = json_response(request, 200)
+ end
+
test "get watchlist id", %{conn: conn} do
auth = build(:auth)
address = insert(:address)
@@ -159,9 +218,9 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
insert(:block, miner: address)
- Chain.transaction_count(address)
- Chain.token_transfers_count(address)
- Chain.gas_usage_count(address)
+ Counters.transaction_count(address)
+ Counters.token_transfers_count(address)
+ Counters.gas_usage_count(address)
request = get(conn, "/api/v2/addresses/#{address.hash}/counters")
@@ -373,6 +432,211 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
check_paginated_response(response_2nd_page, response, txs_from ++ [Enum.at(txs_to, 0)])
end
+
+ test "ignores wrong ordering params", %{conn: conn} do
+ address = insert(:address)
+
+ txs = insert_list(51, :transaction, from_address: address) |> with_block()
+
+ request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"sort" => "foo", "order" => "bar"})
+ assert response = json_response(request, 200)
+
+ request_2nd_page =
+ get(
+ conn,
+ "/api/v2/addresses/#{address.hash}/transactions",
+ %{"sort" => "foo", "order" => "bar"} |> Map.merge(response["next_page_params"])
+ )
+
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ check_paginated_response(response, response_2nd_page, txs)
+ end
+
+ test "backward compatible with legacy paging params", %{conn: conn} do
+ address = insert(:address)
+ block = insert(:block)
+
+ txs = insert_list(51, :transaction, from_address: address) |> with_block(block)
+
+ [_, tx_before_last | _] = txs
+
+ request = get(conn, "/api/v2/addresses/#{address.hash}/transactions")
+ assert response = json_response(request, 200)
+
+ request_2nd_page =
+ get(
+ conn,
+ "/api/v2/addresses/#{address.hash}/transactions",
+ %{"block_number" => to_string(block.number), "index" => to_string(tx_before_last.index)}
+ )
+
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ check_paginated_response(response, response_2nd_page, txs)
+ end
+
+ test "backward compatible with legacy paging params for pending transactions", %{conn: conn} do
+ address = insert(:address)
+
+ txs = insert_list(51, :transaction, from_address: address)
+
+ [_, tx_before_last | _] = txs
+
+ request = get(conn, "/api/v2/addresses/#{address.hash}/transactions")
+ assert response = json_response(request, 200)
+
+ request_2nd_page_pending =
+ get(
+ conn,
+ "/api/v2/addresses/#{address.hash}/transactions",
+ %{"inserted_at" => to_string(tx_before_last.inserted_at), "hash" => to_string(tx_before_last.hash)}
+ )
+
+ assert response_2nd_page_pending = json_response(request_2nd_page_pending, 200)
+
+ check_paginated_response(response, response_2nd_page_pending, txs)
+ end
+
+ test "can order and paginate by fee ascending", %{conn: conn} do
+ address = insert(:address)
+
+ txs_from = insert_list(25, :transaction, from_address: address) |> with_block()
+ txs_to = insert_list(26, :transaction, to_address: address) |> with_block()
+
+ txs =
+ (txs_from ++ txs_to)
+ |> Enum.sort(
+ &(Decimal.compare(&1 |> Chain.fee(:wei) |> elem(1), &2 |> Chain.fee(:wei) |> elem(1)) in [:eq, :lt])
+ )
+
+ request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"sort" => "fee", "order" => "asc"})
+ assert response = json_response(request, 200)
+
+ request_2nd_page =
+ get(
+ conn,
+ "/api/v2/addresses/#{address.hash}/transactions",
+ %{"sort" => "fee", "order" => "asc"} |> Map.merge(response["next_page_params"])
+ )
+
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ assert Enum.count(response["items"]) == 50
+ assert response["next_page_params"] != nil
+ compare_item(Enum.at(txs, 0), Enum.at(response["items"], 0))
+ compare_item(Enum.at(txs, 49), Enum.at(response["items"], 49))
+
+ assert Enum.count(response_2nd_page["items"]) == 1
+ assert response_2nd_page["next_page_params"] == nil
+ compare_item(Enum.at(txs, 50), Enum.at(response_2nd_page["items"], 0))
+
+ check_paginated_response(response, response_2nd_page, txs |> Enum.reverse())
+ end
+
+ test "can order and paginate by fee descending", %{conn: conn} do
+ address = insert(:address)
+
+ txs_from = insert_list(25, :transaction, from_address: address) |> with_block()
+ txs_to = insert_list(26, :transaction, to_address: address) |> with_block()
+
+ txs =
+ (txs_from ++ txs_to)
+ |> Enum.sort(
+ &(Decimal.compare(&1 |> Chain.fee(:wei) |> elem(1), &2 |> Chain.fee(:wei) |> elem(1)) in [:eq, :gt])
+ )
+
+ request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"sort" => "fee", "order" => "desc"})
+ assert response = json_response(request, 200)
+
+ request_2nd_page =
+ get(
+ conn,
+ "/api/v2/addresses/#{address.hash}/transactions",
+ %{"sort" => "fee", "order" => "desc"} |> Map.merge(response["next_page_params"])
+ )
+
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ assert Enum.count(response["items"]) == 50
+ assert response["next_page_params"] != nil
+ compare_item(Enum.at(txs, 0), Enum.at(response["items"], 0))
+ compare_item(Enum.at(txs, 49), Enum.at(response["items"], 49))
+
+ assert Enum.count(response_2nd_page["items"]) == 1
+ assert response_2nd_page["next_page_params"] == nil
+ compare_item(Enum.at(txs, 50), Enum.at(response_2nd_page["items"], 0))
+
+ check_paginated_response(response, response_2nd_page, txs |> Enum.reverse())
+ end
+
+ test "can order and paginate by value ascending", %{conn: conn} do
+ address = insert(:address)
+
+ txs_from = insert_list(25, :transaction, from_address: address) |> with_block()
+ txs_to = insert_list(26, :transaction, to_address: address) |> with_block()
+
+ txs =
+ (txs_from ++ txs_to)
+ |> Enum.sort(&(Decimal.compare(Wei.to(&1.value, :wei), Wei.to(&2.value, :wei)) in [:eq, :lt]))
+
+ request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"sort" => "value", "order" => "asc"})
+ assert response = json_response(request, 200)
+
+ request_2nd_page =
+ get(
+ conn,
+ "/api/v2/addresses/#{address.hash}/transactions",
+ %{"sort" => "value", "order" => "asc"} |> Map.merge(response["next_page_params"])
+ )
+
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ assert Enum.count(response["items"]) == 50
+ assert response["next_page_params"] != nil
+ compare_item(Enum.at(txs, 0), Enum.at(response["items"], 0))
+ compare_item(Enum.at(txs, 49), Enum.at(response["items"], 49))
+
+ assert Enum.count(response_2nd_page["items"]) == 1
+ assert response_2nd_page["next_page_params"] == nil
+ compare_item(Enum.at(txs, 50), Enum.at(response_2nd_page["items"], 0))
+
+ check_paginated_response(response, response_2nd_page, txs |> Enum.reverse())
+ end
+
+ test "can order and paginate by value descending", %{conn: conn} do
+ address = insert(:address)
+
+ txs_from = insert_list(25, :transaction, from_address: address) |> with_block()
+ txs_to = insert_list(26, :transaction, to_address: address) |> with_block()
+
+ txs =
+ (txs_from ++ txs_to)
+ |> Enum.sort(&(Decimal.compare(Wei.to(&1.value, :wei), Wei.to(&2.value, :wei)) in [:eq, :gt]))
+
+ request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"sort" => "value", "order" => "desc"})
+ assert response = json_response(request, 200)
+
+ request_2nd_page =
+ get(
+ conn,
+ "/api/v2/addresses/#{address.hash}/transactions",
+ %{"sort" => "value", "order" => "desc"} |> Map.merge(response["next_page_params"])
+ )
+
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ assert Enum.count(response["items"]) == 50
+ assert response["next_page_params"] != nil
+ compare_item(Enum.at(txs, 0), Enum.at(response["items"], 0))
+ compare_item(Enum.at(txs, 49), Enum.at(response["items"], 49))
+
+ assert Enum.count(response_2nd_page["items"]) == 1
+ assert response_2nd_page["next_page_params"] == nil
+ compare_item(Enum.at(txs, 50), Enum.at(response_2nd_page["items"], 0))
+
+ check_paginated_response(response, response_2nd_page, txs |> Enum.reverse())
+ end
end
describe "/addresses/{address_hash}/token-transfers" do
@@ -1503,10 +1767,10 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
block: tx.block,
block_number: tx.block_number,
address: address,
- first_topic: "0x123456789123456789"
+ first_topic: topic(@first_topic_hex_string_1)
)
- request = get(conn, "/api/v2/addresses/#{address.hash}/logs?topic=0x123456789123456789")
+ request = get(conn, "/api/v2/addresses/#{address.hash}/logs?topic=#{@first_topic_hex_string_1}")
assert response = json_response(request, 200)
assert Enum.count(response["items"]) == 1
assert response["next_page_params"] == nil
@@ -1534,21 +1798,29 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
ctbs_erc_20 =
for _ <- 0..50 do
- insert(:address_current_token_balance_with_token_id, address: address, token_type: "ERC-20", token_id: nil)
+ insert(:address_current_token_balance_with_token_id_and_fixed_token_type,
+ address: address,
+ token_type: "ERC-20",
+ token_id: nil
+ )
|> Repo.preload([:token])
end
- |> Enum.sort_by(fn x -> x.value end, :asc)
+ |> Enum.sort_by(fn x -> Decimal.to_float(Decimal.mult(x.value, x.token.fiat_value)) end, :asc)
ctbs_erc_721 =
for _ <- 0..50 do
- insert(:address_current_token_balance_with_token_id, address: address, token_type: "ERC-721", token_id: nil)
+ insert(:address_current_token_balance_with_token_id_and_fixed_token_type,
+ address: address,
+ token_type: "ERC-721",
+ token_id: nil
+ )
|> Repo.preload([:token])
end
|> Enum.sort_by(fn x -> x.value end, :asc)
ctbs_erc_1155 =
for _ <- 0..50 do
- insert(:address_current_token_balance_with_token_id,
+ insert(:address_current_token_balance_with_token_id_and_fixed_token_type,
address: address,
token_type: "ERC-1155",
token_id: Enum.random(1..100_000)
@@ -1651,7 +1923,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
end
test "check nil", %{conn: conn} do
- address = insert(:address, nonce: 1, fetched_coin_balance: 1)
+ address = insert(:address, transactions_count: 2, fetched_coin_balance: 1)
request = get(conn, "/api/v2/addresses")
@@ -1659,105 +1931,996 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
compare_item(address, address_json)
end
- end
- defp compare_item(%Address{} = address, json) do
- assert Address.checksum(address.hash) == json["hash"]
- assert to_string(address.nonce + 1) == json["tx_count"]
- end
+ test "check smart contract preload", %{conn: conn} do
+ smart_contract = insert(:smart_contract, address_hash: insert(:contract_address, fetched_coin_balance: 1).hash)
- defp compare_item(%Transaction{} = transaction, json) do
- assert to_string(transaction.hash) == json["hash"]
- assert transaction.block_number == json["block"]
- assert to_string(transaction.value.value) == json["value"]
- assert Address.checksum(transaction.from_address_hash) == json["from"]["hash"]
- assert Address.checksum(transaction.to_address_hash) == json["to"]["hash"]
- end
+ request = get(conn, "/api/v2/addresses")
+ assert %{"items" => [address]} = json_response(request, 200)
- defp compare_item(%TokenTransfer{} = token_transfer, json) do
- assert Address.checksum(token_transfer.from_address_hash) == json["from"]["hash"]
- assert Address.checksum(token_transfer.to_address_hash) == json["to"]["hash"]
- assert to_string(token_transfer.transaction_hash) == json["tx_hash"]
- assert json["timestamp"] != nil
- assert json["method"] != nil
- assert to_string(token_transfer.block_hash) == json["block_hash"]
- assert to_string(token_transfer.log_index) == json["log_index"]
- assert check_total(Repo.preload(token_transfer, [{:token, :contract_address}]).token, json["total"], token_transfer)
+ assert String.downcase(address["hash"]) == to_string(smart_contract.address_hash)
+ assert address["is_contract"] == true
+ assert address["is_verified"] == true
+ end
end
- defp compare_item(%InternalTransaction{} = internal_tx, json) do
- assert internal_tx.block_number == json["block"]
- assert to_string(internal_tx.gas) == json["gas_limit"]
- assert internal_tx.index == json["index"]
- assert to_string(internal_tx.transaction_hash) == json["transaction_hash"]
- assert Address.checksum(internal_tx.from_address_hash) == json["from"]["hash"]
- assert Address.checksum(internal_tx.to_address_hash) == json["to"]["hash"]
- end
+ describe "/addresses/{address_hash}/tabs-counters" do
+ test "get 404 on non existing address", %{conn: conn} do
+ address = build(:address)
- defp compare_item(%Block{} = block, json) do
- assert to_string(block.hash) == json["hash"]
- assert block.number == json["height"]
- end
+ request = get(conn, "/api/v2/addresses/#{address.hash}/tabs-counters")
- defp compare_item(%CurrentTokenBalance{} = ctb, json) do
- assert to_string(ctb.value) == json["value"]
- assert (ctb.token_id && to_string(ctb.token_id)) == json["token_id"]
- compare_item(ctb.token, json["token"])
- end
+ assert %{"message" => "Not found"} = json_response(request, 404)
+ end
- defp compare_item(%CoinBalance{} = cb, json) do
- assert to_string(cb.value.value) == json["value"]
- assert cb.block_number == json["block_number"]
+ test "get 422 on invalid address", %{conn: conn} do
+ request = get(conn, "/api/v2/addresses/0x/tabs-counters")
- assert Jason.encode!(Repo.get_by(Block, number: cb.block_number).timestamp) =~
- String.replace(json["block_timestamp"], "Z", "")
- end
+ assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422)
+ end
- defp compare_item(%Token{} = token, json) do
- assert Address.checksum(token.contract_address_hash) == json["address"]
- assert to_string(token.symbol) == json["symbol"]
- assert to_string(token.name) == json["name"]
- assert to_string(token.type) == json["type"]
- assert to_string(token.decimals) == json["decimals"]
- assert (token.holder_count && to_string(token.holder_count)) == json["holders"]
- assert Map.has_key?(json, "exchange_rate")
- end
+ test "get counters with 0s", %{conn: conn} do
+ address = insert(:address)
- defp compare_item(%Log{} = log, json) do
- assert log.index == json["index"]
- assert to_string(log.data) == json["data"]
- assert Address.checksum(log.address_hash) == json["address"]["hash"]
- assert to_string(log.transaction_hash) == json["tx_hash"]
- end
+ request = get(conn, "/api/v2/addresses/#{address.hash}/tabs-counters")
- defp compare_item(%Withdrawal{} = withdrawal, json) do
- assert withdrawal.index == json["index"]
- end
+ assert %{
+ "validations_count" => 0,
+ "transactions_count" => 0,
+ "token_transfers_count" => 0,
+ "token_balances_count" => 0,
+ "logs_count" => 0,
+ "withdrawals_count" => 0,
+ "internal_txs_count" => 0
+ } = json_response(request, 200)
+ end
- defp check_paginated_response(first_page_resp, second_page_resp, list) do
- assert Enum.count(first_page_resp["items"]) == 50
- assert first_page_resp["next_page_params"] != nil
- compare_item(Enum.at(list, 50), Enum.at(first_page_resp["items"], 0))
- compare_item(Enum.at(list, 1), Enum.at(first_page_resp["items"], 49))
+ test "get counters and check that cache works", %{conn: conn} do
+ address = insert(:address, withdrawals: insert_list(60, :withdrawal))
- assert Enum.count(second_page_resp["items"]) == 1
- assert second_page_resp["next_page_params"] == nil
- compare_item(Enum.at(list, 0), Enum.at(second_page_resp["items"], 0))
- end
+ insert(:transaction, from_address: address) |> with_block()
+ insert(:transaction, to_address: address) |> with_block()
+ another_tx = insert(:transaction) |> with_block()
- def check_total(%Token{type: nft}, json, token_transfer) when nft in ["ERC-1155"] do
- json["token_id"] in Enum.map(token_transfer.token_ids, fn x -> to_string(x) end) and
- json["value"] == to_string(token_transfer.amount)
- end
+ insert(:token_transfer,
+ from_address: address,
+ transaction: another_tx,
+ block: another_tx.block,
+ block_number: another_tx.block_number
+ )
- def check_total(%Token{type: nft}, json, token_transfer) when nft in ["ERC-721"] do
- json["token_id"] in Enum.map(token_transfer.token_ids, fn x -> to_string(x) end)
- end
+ insert(:token_transfer,
+ to_address: address,
+ transaction: another_tx,
+ block: another_tx.block,
+ block_number: another_tx.block_number
+ )
+
+ insert(:block, miner: address)
+
+ tx =
+ :transaction
+ |> insert()
+ |> with_block()
+
+ for x <- 1..2 do
+ insert(:internal_transaction,
+ transaction: tx,
+ index: x,
+ block_number: tx.block_number,
+ transaction_index: tx.index,
+ block_hash: tx.block_hash,
+ block_index: x,
+ from_address: address
+ )
+ end
+
+ for _ <- 0..60 do
+ insert(:address_current_token_balance_with_token_id, address: address)
+ end
+
+ for x <- 0..60 do
+ tx =
+ :transaction
+ |> insert()
+ |> with_block()
+
+ insert(:log,
+ transaction: tx,
+ index: x,
+ block: tx.block,
+ block_number: tx.block_number,
+ address: address
+ )
+ end
+
+ request = get(conn, "/api/v2/addresses/#{address.hash}/tabs-counters")
+
+ assert %{
+ "validations_count" => 1,
+ "transactions_count" => 2,
+ "token_transfers_count" => 2,
+ "token_balances_count" => 51,
+ "logs_count" => 51,
+ "withdrawals_count" => 51,
+ "internal_txs_count" => 2
+ } = json_response(request, 200)
+
+ for x <- 3..4 do
+ insert(:internal_transaction,
+ transaction: tx,
+ index: x,
+ block_number: tx.block_number,
+ transaction_index: tx.index,
+ block_hash: tx.block_hash,
+ block_index: x,
+ from_address: address
+ )
+ end
+
+ request = get(conn, "/api/v2/addresses/#{address.hash}/tabs-counters")
+
+ assert %{
+ "validations_count" => 1,
+ "transactions_count" => 2,
+ "token_transfers_count" => 2,
+ "token_balances_count" => 51,
+ "logs_count" => 51,
+ "withdrawals_count" => 51,
+ "internal_txs_count" => 2
+ } = json_response(request, 200)
+ end
+
+ test "check counters cache ttl", %{conn: conn} do
+ address = insert(:address, withdrawals: insert_list(60, :withdrawal))
+
+ insert(:transaction, from_address: address) |> with_block()
+ insert(:transaction, to_address: address) |> with_block()
+ another_tx = insert(:transaction) |> with_block()
+
+ insert(:token_transfer,
+ from_address: address,
+ transaction: another_tx,
+ block: another_tx.block,
+ block_number: another_tx.block_number
+ )
+
+ insert(:token_transfer,
+ to_address: address,
+ transaction: another_tx,
+ block: another_tx.block,
+ block_number: another_tx.block_number
+ )
+
+ insert(:block, miner: address)
+
+ tx =
+ :transaction
+ |> insert()
+ |> with_block()
+
+ for x <- 1..2 do
+ insert(:internal_transaction,
+ transaction: tx,
+ index: x,
+ block_number: tx.block_number,
+ transaction_index: tx.index,
+ block_hash: tx.block_hash,
+ block_index: x,
+ from_address: address
+ )
+ end
+
+ for _ <- 0..60 do
+ insert(:address_current_token_balance_with_token_id, address: address)
+ end
+
+ for x <- 0..60 do
+ tx =
+ :transaction
+ |> insert()
+ |> with_block()
+
+ insert(:log,
+ transaction: tx,
+ index: x,
+ block: tx.block,
+ block_number: tx.block_number,
+ address: address
+ )
+ end
+
+ request = get(conn, "/api/v2/addresses/#{address.hash}/tabs-counters")
+
+ assert %{
+ "validations_count" => 1,
+ "transactions_count" => 2,
+ "token_transfers_count" => 2,
+ "token_balances_count" => 51,
+ "logs_count" => 51,
+ "withdrawals_count" => 51,
+ "internal_txs_count" => 2
+ } = json_response(request, 200)
+
+ old_env = Application.get_env(:explorer, Explorer.Chain.Cache.AddressesTabsCounters)
+ Application.put_env(:explorer, Explorer.Chain.Cache.AddressesTabsCounters, ttl: 200)
+ :timer.sleep(200)
+
+ for x <- 3..4 do
+ insert(:internal_transaction,
+ transaction: tx,
+ index: x,
+ block_number: tx.block_number,
+ transaction_index: tx.index,
+ block_hash: tx.block_hash,
+ block_index: x,
+ from_address: address
+ )
+ end
+
+ insert(:transaction, from_address: address) |> with_block()
+ insert(:transaction, to_address: address) |> with_block()
+
+ request = get(conn, "/api/v2/addresses/#{address.hash}/tabs-counters")
+
+ assert %{
+ "validations_count" => 1,
+ "transactions_count" => 4,
+ "token_transfers_count" => 2,
+ "token_balances_count" => 51,
+ "logs_count" => 51,
+ "withdrawals_count" => 51,
+ "internal_txs_count" => 4
+ } = json_response(request, 200)
+
+ Application.put_env(:explorer, Explorer.Chain.Cache.AddressesTabsCounters, old_env)
+ end
+ end
+
+ describe "/addresses/{address_hash}/nft" do
+ setup do
+ {:ok, endpoint: &"/api/v2/addresses/#{&1}/nft"}
+ end
+
+ test "get 404 on non existing address", %{conn: conn, endpoint: endpoint} do
+ address = build(:address)
+
+ request = get(conn, endpoint.(address.hash))
+
+ assert %{"message" => "Not found"} = json_response(request, 404)
+ end
+
+ test "get 422 on invalid address", %{conn: conn, endpoint: endpoint} do
+ request = get(conn, endpoint.("0x"))
+
+ assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422)
+ end
+
+ test "get paginated ERC-721 nft", %{conn: conn, endpoint: endpoint} do
+ address = insert(:address)
+
+ insert_list(51, :token_instance)
+
+ token_instances =
+ for _ <- 0..50 do
+ erc_721_token = insert(:token, type: "ERC-721")
+
+ insert(:token_instance,
+ owner_address_hash: address.hash,
+ token_contract_address_hash: erc_721_token.contract_address_hash
+ )
+ |> Repo.preload([:token])
+ end
+ # works because one token_id per token, despite ordering in DB: [asc: ti.token_contract_address_hash, desc: ti.token_id]
+ |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id}, :desc)
+
+ request = get(conn, endpoint.(address.hash))
+ assert response = json_response(request, 200)
+
+ request_2nd_page = get(conn, endpoint.(address.hash), response["next_page_params"])
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ check_paginated_response(response, response_2nd_page, token_instances)
+ end
+
+ test "get paginated ERC-1155 nft", %{conn: conn, endpoint: endpoint} do
+ address = insert(:address)
+
+ insert_list(51, :address_current_token_balance_with_token_id)
+
+ token_instances =
+ for _ <- 0..50 do
+ token = insert(:token, type: "ERC-1155")
+
+ ti =
+ insert(:token_instance,
+ token_contract_address_hash: token.contract_address_hash
+ )
+ |> Repo.preload([:token])
+
+ current_token_balance =
+ insert(:address_current_token_balance_with_token_id_and_fixed_token_type,
+ address: address,
+ token_type: "ERC-1155",
+ token_id: ti.token_id,
+ token_contract_address_hash: token.contract_address_hash
+ )
+
+ %Instance{ti | current_token_balance: current_token_balance}
+ end
+ |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id}, :desc)
+
+ request = get(conn, endpoint.(address.hash))
+ assert response = json_response(request, 200)
+
+ request_2nd_page = get(conn, endpoint.(address.hash), response["next_page_params"])
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ check_paginated_response(response, response_2nd_page, token_instances)
+ end
+
+ test "test filters", %{conn: conn, endpoint: endpoint} do
+ address = insert(:address)
+
+ insert_list(51, :token_instance)
+
+ token_instances_721 =
+ for _ <- 0..50 do
+ erc_721_token = insert(:token, type: "ERC-721")
+
+ insert(:token_instance,
+ owner_address_hash: address.hash,
+ token_contract_address_hash: erc_721_token.contract_address_hash
+ )
+ |> Repo.preload([:token])
+ end
+ |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id}, :desc)
+
+ insert_list(51, :address_current_token_balance_with_token_id)
+
+ token_instances_1155 =
+ for _ <- 0..50 do
+ token = insert(:token, type: "ERC-1155")
+
+ ti =
+ insert(:token_instance,
+ token_contract_address_hash: token.contract_address_hash
+ )
+ |> Repo.preload([:token])
+
+ current_token_balance =
+ insert(:address_current_token_balance_with_token_id_and_fixed_token_type,
+ address: address,
+ token_type: "ERC-1155",
+ token_id: ti.token_id,
+ token_contract_address_hash: token.contract_address_hash
+ )
+
+ %Instance{ti | current_token_balance: current_token_balance}
+ end
+ |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id}, :desc)
+
+ filter = %{"type" => "ERC-721"}
+ request = get(conn, endpoint.(address.hash), filter)
+ assert response = json_response(request, 200)
+
+ request_2nd_page = get(conn, endpoint.(address.hash), Map.merge(response["next_page_params"], filter))
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ check_paginated_response(response, response_2nd_page, token_instances_721)
+
+ filter = %{"type" => "ERC-1155"}
+ request = get(conn, endpoint.(address.hash), filter)
+ assert response = json_response(request, 200)
+
+ request_2nd_page = get(conn, endpoint.(address.hash), Map.merge(response["next_page_params"], filter))
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ check_paginated_response(response, response_2nd_page, token_instances_1155)
+ end
+
+ test "return all token instances", %{conn: conn, endpoint: endpoint} do
+ address = insert(:address)
+
+ insert_list(51, :token_instance)
+
+ token_instances_721 =
+ for _ <- 0..50 do
+ erc_721_token = insert(:token, type: "ERC-721")
+
+ insert(:token_instance,
+ owner_address_hash: address.hash,
+ token_contract_address_hash: erc_721_token.contract_address_hash
+ )
+ |> Repo.preload([:token])
+ end
+ |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id}, :desc)
+
+ insert_list(51, :address_current_token_balance_with_token_id)
+
+ token_instances_1155 =
+ for _ <- 0..50 do
+ token = insert(:token, type: "ERC-1155")
+
+ ti =
+ insert(:token_instance,
+ token_contract_address_hash: token.contract_address_hash
+ )
+ |> Repo.preload([:token])
+
+ current_token_balance =
+ insert(:address_current_token_balance_with_token_id_and_fixed_token_type,
+ address: address,
+ token_type: "ERC-1155",
+ token_id: ti.token_id,
+ token_contract_address_hash: token.contract_address_hash
+ )
+
+ %Instance{ti | current_token_balance: current_token_balance}
+ end
+ |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id}, :desc)
+
+ request = get(conn, endpoint.(address.hash))
+ assert response = json_response(request, 200)
+
+ request_2nd_page = get(conn, endpoint.(address.hash), response["next_page_params"])
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ request_3rd_page = get(conn, endpoint.(address.hash), response_2nd_page["next_page_params"])
+ assert response_3rd_page = json_response(request_3rd_page, 200)
+
+ assert response["next_page_params"] != nil
+ assert response_2nd_page["next_page_params"] != nil
+ assert response_3rd_page["next_page_params"] == nil
+
+ assert Enum.count(response["items"]) == 50
+ assert Enum.count(response_2nd_page["items"]) == 50
+ assert Enum.count(response_3rd_page["items"]) == 2
+
+ compare_item(Enum.at(token_instances_721, 50), Enum.at(response["items"], 0))
+ compare_item(Enum.at(token_instances_721, 1), Enum.at(response["items"], 49))
+
+ compare_item(Enum.at(token_instances_721, 0), Enum.at(response_2nd_page["items"], 0))
+ compare_item(Enum.at(token_instances_1155, 50), Enum.at(response_2nd_page["items"], 1))
+ compare_item(Enum.at(token_instances_1155, 2), Enum.at(response_2nd_page["items"], 49))
+
+ compare_item(Enum.at(token_instances_1155, 1), Enum.at(response_3rd_page["items"], 0))
+ compare_item(Enum.at(token_instances_1155, 0), Enum.at(response_3rd_page["items"], 1))
+ end
+ end
+
+ describe "/addresses/{address_hash}/nft/collections" do
+ setup do
+ {:ok, endpoint: &"/api/v2/addresses/#{&1}/nft/collections"}
+ end
+
+ test "get 404 on non existing address", %{conn: conn, endpoint: endpoint} do
+ address = build(:address)
+
+ request = get(conn, endpoint.(address.hash))
+
+ assert %{"message" => "Not found"} = json_response(request, 404)
+ end
+
+ test "get 422 on invalid address", %{conn: conn, endpoint: endpoint} do
+ request = get(conn, endpoint.("0x"))
+
+ assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422)
+ end
+
+ test "get paginated erc-721 collection", %{conn: conn, endpoint: endpoint} do
+ address = insert(:address)
+
+ insert_list(51, :address_current_token_balance_with_token_id)
+ insert_list(51, :token_instance)
+
+ ctbs =
+ for _ <- 0..50 do
+ token = insert(:token, type: "ERC-721")
+ amount = Enum.random(16..50)
+
+ current_token_balance =
+ insert(:address_current_token_balance,
+ address: address,
+ token_type: "ERC-721",
+ token_id: nil,
+ token_contract_address_hash: token.contract_address_hash,
+ value: amount
+ )
+ |> Repo.preload([:token])
+
+ token_instances =
+ for _ <- 0..(amount - 1) do
+ ti =
+ insert(:token_instance,
+ token_contract_address_hash: token.contract_address_hash,
+ owner_address_hash: address.hash
+ )
+ |> Repo.preload([:token])
+
+ %Instance{ti | current_token_balance: current_token_balance}
+ end
+ |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id}, :desc)
+
+ {current_token_balance, token_instances}
+ end
+ |> Enum.sort_by(&elem(&1, 0).token_contract_address_hash, :desc)
+
+ request = get(conn, endpoint.(address.hash))
+ assert response = json_response(request, 200)
+
+ request_2nd_page = get(conn, endpoint.(address.hash), response["next_page_params"])
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ check_paginated_response(response, response_2nd_page, ctbs)
+ end
+
+ test "get paginated erc-1155 collection", %{conn: conn, endpoint: endpoint} do
+ address = insert(:address)
+
+ insert_list(51, :address_current_token_balance_with_token_id)
+ insert_list(51, :token_instance)
+
+ collections =
+ for _ <- 0..50 do
+ token = insert(:token, type: "ERC-1155")
+ amount = Enum.random(16..50)
+
+ token_instances =
+ for _ <- 0..(amount - 1) do
+ ti =
+ insert(:token_instance,
+ token_contract_address_hash: token.contract_address_hash,
+ owner_address_hash: address.hash
+ )
+ |> Repo.preload([:token])
+
+ current_token_balance =
+ insert(:address_current_token_balance,
+ address: address,
+ token_type: "ERC-1155",
+ token_id: ti.token_id,
+ token_contract_address_hash: token.contract_address_hash,
+ value: Enum.random(1..100_000)
+ )
+ |> Repo.preload([:token])
+
+ %Instance{ti | current_token_balance: current_token_balance}
+ end
+ |> Enum.sort_by(& &1.token_id, :desc)
+
+ {token, amount, token_instances}
+ end
+ |> Enum.sort_by(&elem(&1, 0).contract_address_hash, :desc)
+
+ request = get(conn, endpoint.(address.hash))
+ assert response = json_response(request, 200)
+
+ request_2nd_page = get(conn, endpoint.(address.hash), response["next_page_params"])
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ check_paginated_response(response, response_2nd_page, collections)
+ end
+
+ test "test filters", %{conn: conn, endpoint: endpoint} do
+ address = insert(:address)
+
+ insert_list(51, :address_current_token_balance_with_token_id)
+ insert_list(51, :token_instance)
+
+ ctbs =
+ for _ <- 0..50 do
+ token = insert(:token, type: "ERC-721")
+ amount = Enum.random(16..50)
+
+ current_token_balance =
+ insert(:address_current_token_balance,
+ address: address,
+ token_type: "ERC-721",
+ token_id: nil,
+ token_contract_address_hash: token.contract_address_hash,
+ value: amount
+ )
+ |> Repo.preload([:token])
+
+ token_instances =
+ for _ <- 0..(amount - 1) do
+ ti =
+ insert(:token_instance,
+ token_contract_address_hash: token.contract_address_hash,
+ owner_address_hash: address.hash
+ )
+ |> Repo.preload([:token])
+
+ %Instance{ti | current_token_balance: current_token_balance}
+ end
+ |> Enum.sort_by(& &1.token_id, :desc)
+
+ {current_token_balance, token_instances}
+ end
+ |> Enum.sort_by(&elem(&1, 0).token_contract_address_hash, :desc)
+
+ collections =
+ for _ <- 0..50 do
+ token = insert(:token, type: "ERC-1155")
+ amount = Enum.random(16..50)
+
+ token_instances =
+ for _ <- 0..(amount - 1) do
+ ti =
+ insert(:token_instance,
+ token_contract_address_hash: token.contract_address_hash,
+ owner_address_hash: address.hash
+ )
+ |> Repo.preload([:token])
+
+ current_token_balance =
+ insert(:address_current_token_balance,
+ address: address,
+ token_type: "ERC-1155",
+ token_id: ti.token_id,
+ token_contract_address_hash: token.contract_address_hash,
+ value: Enum.random(1..100_000)
+ )
+ |> Repo.preload([:token])
+
+ %Instance{ti | current_token_balance: current_token_balance}
+ end
+ |> Enum.sort_by(& &1.token_id, :desc)
+
+ {token, amount, token_instances}
+ end
+ |> Enum.sort_by(&elem(&1, 0).contract_address_hash, :desc)
+
+ filter = %{"type" => "ERC-721"}
+ request = get(conn, endpoint.(address.hash), filter)
+ assert response = json_response(request, 200)
+
+ request_2nd_page = get(conn, endpoint.(address.hash), Map.merge(response["next_page_params"], filter))
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ check_paginated_response(response, response_2nd_page, ctbs)
+
+ filter = %{"type" => "ERC-1155"}
+ request = get(conn, endpoint.(address.hash), filter)
+ assert response = json_response(request, 200)
+
+ request_2nd_page = get(conn, endpoint.(address.hash), Map.merge(response["next_page_params"], filter))
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ check_paginated_response(response, response_2nd_page, collections)
+ end
+
+ test "return all collections", %{conn: conn, endpoint: endpoint} do
+ address = insert(:address)
+
+ insert_list(51, :address_current_token_balance_with_token_id)
+ insert_list(51, :token_instance)
+
+ collections_721 =
+ for _ <- 0..50 do
+ token = insert(:token, type: "ERC-721")
+ amount = Enum.random(16..50)
+
+ current_token_balance =
+ insert(:address_current_token_balance,
+ address: address,
+ token_type: "ERC-721",
+ token_id: nil,
+ token_contract_address_hash: token.contract_address_hash,
+ value: amount
+ )
+ |> Repo.preload([:token])
+
+ token_instances =
+ for _ <- 0..(amount - 1) do
+ ti =
+ insert(:token_instance,
+ token_contract_address_hash: token.contract_address_hash,
+ owner_address_hash: address.hash
+ )
+ |> Repo.preload([:token])
+
+ %Instance{ti | current_token_balance: current_token_balance}
+ end
+ |> Enum.sort_by(& &1.token_id, :desc)
+
+ {current_token_balance, token_instances}
+ end
+ |> Enum.sort_by(&elem(&1, 0).token_contract_address_hash, :desc)
+
+ collections_1155 =
+ for _ <- 0..50 do
+ token = insert(:token, type: "ERC-1155")
+ amount = Enum.random(16..50)
+
+ token_instances =
+ for _ <- 0..(amount - 1) do
+ ti =
+ insert(:token_instance,
+ token_contract_address_hash: token.contract_address_hash,
+ owner_address_hash: address.hash
+ )
+ |> Repo.preload([:token])
+
+ current_token_balance =
+ insert(:address_current_token_balance,
+ address: address,
+ token_type: "ERC-1155",
+ token_id: ti.token_id,
+ token_contract_address_hash: token.contract_address_hash,
+ value: Enum.random(1..100_000)
+ )
+ |> Repo.preload([:token])
+
+ %Instance{ti | current_token_balance: current_token_balance}
+ end
+ |> Enum.sort_by(& &1.token_id, :desc)
+
+ {token, amount, token_instances}
+ end
+ |> Enum.sort_by(&elem(&1, 0).contract_address_hash, :desc)
+
+ request = get(conn, endpoint.(address.hash))
+ assert response = json_response(request, 200)
+
+ request_2nd_page = get(conn, endpoint.(address.hash), response["next_page_params"])
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ request_3rd_page = get(conn, endpoint.(address.hash), response_2nd_page["next_page_params"])
+ assert response_3rd_page = json_response(request_3rd_page, 200)
+
+ assert response["next_page_params"] != nil
+ assert response_2nd_page["next_page_params"] != nil
+ assert response_3rd_page["next_page_params"] == nil
+
+ assert Enum.count(response["items"]) == 50
+ assert Enum.count(response_2nd_page["items"]) == 50
+ assert Enum.count(response_3rd_page["items"]) == 2
+
+ compare_item(Enum.at(collections_721, 50), Enum.at(response["items"], 0))
+ compare_item(Enum.at(collections_721, 1), Enum.at(response["items"], 49))
+
+ compare_item(Enum.at(collections_721, 0), Enum.at(response_2nd_page["items"], 0))
+ compare_item(Enum.at(collections_1155, 50), Enum.at(response_2nd_page["items"], 1))
+ compare_item(Enum.at(collections_1155, 2), Enum.at(response_2nd_page["items"], 49))
+
+ compare_item(Enum.at(collections_1155, 1), Enum.at(response_3rd_page["items"], 0))
+ compare_item(Enum.at(collections_1155, 0), Enum.at(response_3rd_page["items"], 1))
+ end
+ end
+
+ defp compare_item(%Address{} = address, json) do
+ assert Address.checksum(address.hash) == json["hash"]
+ assert to_string(address.transactions_count) == json["tx_count"]
+ end
+
+ defp compare_item(%Transaction{} = transaction, json) do
+ assert to_string(transaction.hash) == json["hash"]
+ assert transaction.block_number == json["block"]
+ assert to_string(transaction.value.value) == json["value"]
+ assert Address.checksum(transaction.from_address_hash) == json["from"]["hash"]
+ assert Address.checksum(transaction.to_address_hash) == json["to"]["hash"]
+ end
+
+ defp compare_item(%TokenTransfer{} = token_transfer, json) do
+ assert Address.checksum(token_transfer.from_address_hash) == json["from"]["hash"]
+ assert Address.checksum(token_transfer.to_address_hash) == json["to"]["hash"]
+ assert to_string(token_transfer.transaction_hash) == json["tx_hash"]
+ assert json["timestamp"] != nil
+ assert json["method"] != nil
+ assert to_string(token_transfer.block_hash) == json["block_hash"]
+ assert to_string(token_transfer.log_index) == json["log_index"]
+ assert check_total(Repo.preload(token_transfer, [{:token, :contract_address}]).token, json["total"], token_transfer)
+ end
+
+ defp compare_item(%InternalTransaction{} = internal_tx, json) do
+ assert internal_tx.block_number == json["block"]
+ assert to_string(internal_tx.gas) == json["gas_limit"]
+ assert internal_tx.index == json["index"]
+ assert to_string(internal_tx.transaction_hash) == json["transaction_hash"]
+ assert Address.checksum(internal_tx.from_address_hash) == json["from"]["hash"]
+ assert Address.checksum(internal_tx.to_address_hash) == json["to"]["hash"]
+ end
+
+ defp compare_item(%Block{} = block, json) do
+ assert to_string(block.hash) == json["hash"]
+ assert block.number == json["height"]
+ end
+
+ defp compare_item(%CurrentTokenBalance{} = ctb, json) do
+ assert to_string(ctb.value) == json["value"]
+ assert (ctb.token_id && to_string(ctb.token_id)) == json["token_id"]
+ compare_item(ctb.token, json["token"])
+ end
+
+ defp compare_item(%CoinBalance{} = cb, json) do
+ assert to_string(cb.value.value) == json["value"]
+ assert cb.block_number == json["block_number"]
+
+ assert Jason.encode!(Repo.get_by(Block, number: cb.block_number).timestamp) =~
+ String.replace(json["block_timestamp"], "Z", "")
+ end
+
+ defp compare_item(%Token{} = token, json) do
+ assert Address.checksum(token.contract_address_hash) == json["address"]
+ assert to_string(token.symbol) == json["symbol"]
+ assert to_string(token.name) == json["name"]
+ assert to_string(token.type) == json["type"]
+ assert to_string(token.decimals) == json["decimals"]
+ assert (token.holder_count && to_string(token.holder_count)) == json["holders"]
+ assert Map.has_key?(json, "exchange_rate")
+ end
+
+ defp compare_item(%Log{} = log, json) do
+ assert log.index == json["index"]
+ assert to_string(log.data) == json["data"]
+ assert Address.checksum(log.address_hash) == json["address"]["hash"]
+ assert to_string(log.transaction_hash) == json["tx_hash"]
+ assert json["block_number"] == log.block_number
+ assert json["block_hash"] == to_string(log.block_hash)
+ end
+
+ defp compare_item(%Withdrawal{} = withdrawal, json) do
+ assert withdrawal.index == json["index"]
+ end
+
+ defp compare_item(%Instance{token: %Token{} = token} = instance, json) do
+ token_type = token.type
+ value = to_string(value(token.type, instance))
+ id = to_string(instance.token_id)
+ metadata = instance.metadata
+ token_address_hash = Address.checksum(token.contract_address_hash)
+ app_url = instance.metadata["external_url"]
+ animation_url = instance.metadata["animation_url"]
+ image_url = instance.metadata["image_url"]
+ token_name = token.name
+
+ assert %{
+ "token_type" => ^token_type,
+ "value" => ^value,
+ "id" => ^id,
+ "metadata" => ^metadata,
+ "owner" => nil,
+ "token" => %{"address" => ^token_address_hash, "name" => ^token_name, "type" => ^token_type},
+ "external_app_url" => ^app_url,
+ "animation_url" => ^animation_url,
+ "image_url" => ^image_url,
+ "is_unique" => nil
+ } = json
+ end
+
+ defp compare_item({%CurrentTokenBalance{token: token} = ctb, token_instances}, json) do
+ token_type = token.type
+ token_address_hash = Address.checksum(token.contract_address_hash)
+ token_name = token.name
+ amount = to_string(ctb.distinct_token_instances_count || ctb.value)
+
+ assert Enum.count(json["token_instances"]) == @instances_amount_in_collection
+
+ token_instances
+ |> Enum.take(@instances_amount_in_collection)
+ |> Enum.with_index()
+ |> Enum.each(fn {instance, index} ->
+ compare_token_instance_in_collection(instance, Enum.at(json["token_instances"], index))
+ end)
+
+ assert %{
+ "token" => %{"address" => ^token_address_hash, "name" => ^token_name, "type" => ^token_type},
+ "amount" => ^amount
+ } = json
+ end
+
+ defp compare_item({token, amount, token_instances}, json) do
+ token_type = token.type
+ token_address_hash = Address.checksum(token.contract_address_hash)
+ token_name = token.name
+ amount = to_string(amount)
+
+ assert Enum.count(json["token_instances"]) == @instances_amount_in_collection
+
+ token_instances
+ |> Enum.take(@instances_amount_in_collection)
+ |> Enum.with_index()
+ |> Enum.each(fn {instance, index} ->
+ compare_token_instance_in_collection(instance, Enum.at(json["token_instances"], index))
+ end)
+
+ assert %{
+ "token" => %{"address" => ^token_address_hash, "name" => ^token_name, "type" => ^token_type},
+ "amount" => ^amount
+ } = json
+ end
+
+ defp compare_token_instance_in_collection(%Instance{token: %Token{} = token} = instance, json) do
+ token_type = token.type
+ value = to_string(value(token.type, instance))
+ id = to_string(instance.token_id)
+ metadata = instance.metadata
+ app_url = instance.metadata["external_url"]
+ animation_url = instance.metadata["animation_url"]
+ image_url = instance.metadata["image_url"]
+
+ assert %{
+ "token_type" => ^token_type,
+ "value" => ^value,
+ "id" => ^id,
+ "metadata" => ^metadata,
+ "owner" => nil,
+ "token" => nil,
+ "external_app_url" => ^app_url,
+ "animation_url" => ^animation_url,
+ "image_url" => ^image_url,
+ "is_unique" => nil
+ } = json
+ end
+
+ defp value("ERC-721", _), do: 1
+ defp value(_, nft), do: nft.current_token_balance.value
+
+ defp check_paginated_response(first_page_resp, second_page_resp, list) do
+ assert Enum.count(first_page_resp["items"]) == 50
+ assert first_page_resp["next_page_params"] != nil
+ compare_item(Enum.at(list, 50), Enum.at(first_page_resp["items"], 0))
+ compare_item(Enum.at(list, 1), Enum.at(first_page_resp["items"], 49))
+
+ assert Enum.count(second_page_resp["items"]) == 1
+ assert second_page_resp["next_page_params"] == nil
+ compare_item(Enum.at(list, 0), Enum.at(second_page_resp["items"], 0))
+ end
# with the current implementation no transfers should come with list in totals
def check_total(%Token{type: nft}, json, _token_transfer) when nft in ["ERC-721", "ERC-1155"] and is_list(json) do
false
end
+ def check_total(%Token{type: nft}, json, token_transfer) when nft in ["ERC-1155"] do
+ json["token_id"] in Enum.map(token_transfer.token_ids, fn x -> to_string(x) end) and
+ json["value"] == to_string(token_transfer.amount)
+ end
+
+ def check_total(%Token{type: nft}, json, token_transfer) when nft in ["ERC-721"] do
+ json["token_id"] in Enum.map(token_transfer.token_ids, fn x -> to_string(x) end)
+ end
+
def check_total(_, _, _), do: true
+
+ def get_eip1967_implementation_non_zero_address do
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000001"}
+ end)
+ end
end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/block_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/block_controller_test.exs
index 9e115db4bd65..5a29e5168366 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/block_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/block_controller_test.exs
@@ -103,6 +103,8 @@ defmodule BlockScoutWeb.API.V2.BlockControllerTest do
|> insert_list(:block, consensus: false)
|> Enum.reverse()
+ Enum.each(reorgs, fn b -> insert(:block, number: b.number, consensus: true) end)
+
request = get(conn, "/api/v2/blocks", %{"type" => "reorg"})
assert response = json_response(request, 200)
@@ -119,6 +121,8 @@ defmodule BlockScoutWeb.API.V2.BlockControllerTest do
51
|> insert_list(:block, consensus: false)
+ Enum.each(reorgs, fn b -> insert(:block, number: b.number, consensus: true) end)
+
filter = %{"type" => "reorg"}
request = get(conn, "/api/v2/blocks", filter)
assert response = json_response(request, 200)
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/import_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/import_controller_test.exs
index 8673958f4f9a..52ea55071b61 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/import_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/import_controller_test.exs
@@ -3,14 +3,20 @@ defmodule BlockScoutWeb.API.V2.ImportControllerTest do
describe "/import/token-info" do
test "return error on misconfigured api key", %{conn: conn} do
- request = post(conn, "/api/v2/import/token-info", %{"iconUrl" => "abc", "tokenAddress" => build(:address).hash})
+ request =
+ post(conn, "/api/v2/import/token-info", %{
+ "iconUrl" => "abc",
+ "tokenAddress" => build(:address).hash,
+ "tokenSymbol" => "",
+ "tokenName" => ""
+ })
assert %{"message" => "API key not configured on the server"} = json_response(request, 403)
end
test "return error on wrong api key", %{conn: conn} do
Application.put_env(:block_scout_web, :sensitive_endpoints_api_key, "abc")
- body = %{"iconUrl" => "abc", "tokenAddress" => build(:address).hash}
+ body = %{"iconUrl" => "abc", "tokenAddress" => build(:address).hash, "tokenSymbol" => "", "tokenName" => ""}
request = post(conn, "/api/v2/import/token-info", Map.merge(body, %{"api_key" => "123"}))
assert %{"message" => "Wrong API key"} = json_response(request, 401)
@@ -24,15 +30,19 @@ defmodule BlockScoutWeb.API.V2.ImportControllerTest do
Application.put_env(:block_scout_web, :sensitive_endpoints_api_key, api_key)
- token_address = to_string(insert(:token).contract_address_hash)
+ token = insert(:token, icon_url: nil)
+ token_address = to_string(token.contract_address_hash)
- body = %{"iconUrl" => icon_url, "tokenAddress" => token_address}
+ body = %{"iconUrl" => icon_url, "tokenAddress" => token_address, "tokenSymbol" => "", "tokenName" => ""}
request = post(conn, "/api/v2/import/token-info", Map.merge(body, %{"api_key" => api_key}))
- assert %{"message" => "Invalid URL"} = json_response(request, 422)
+ assert %{"message" => "Success"} = json_response(request, 200)
request = get(conn, "/api/v2/tokens/#{token_address}")
- assert %{"icon_url" => nil} = json_response(request, 200)
+
+ name = token.name
+ symbol = token.symbol
+ assert %{"icon_url" => nil, "name" => ^name, "symbol" => ^symbol} = json_response(request, 200)
Application.put_env(:block_scout_web, :sensitive_endpoints_api_key, nil)
end
@@ -40,18 +50,25 @@ defmodule BlockScoutWeb.API.V2.ImportControllerTest do
test "success import token info", %{conn: conn} do
api_key = "abc123"
icon_url = "http://example.com/image?a=0&b=1"
+ token_symbol = "UPD"
+ token_name = "UPDATED"
Application.put_env(:block_scout_web, :sensitive_endpoints_api_key, api_key)
token_address = to_string(insert(:token).contract_address_hash)
- body = %{"iconUrl" => icon_url, "tokenAddress" => token_address}
+ body = %{
+ "iconUrl" => icon_url,
+ "tokenAddress" => token_address,
+ "tokenSymbol" => token_symbol,
+ "tokenName" => token_name
+ }
request = post(conn, "/api/v2/import/token-info", Map.merge(body, %{"api_key" => api_key}))
assert %{"message" => "Success"} = json_response(request, 200)
request = get(conn, "/api/v2/tokens/#{token_address}")
- assert %{"icon_url" => ^icon_url} = json_response(request, 200)
+ assert %{"icon_url" => ^icon_url, "name" => ^token_name, "symbol" => ^token_symbol} = json_response(request, 200)
Application.put_env(:block_scout_web, :sensitive_endpoints_api_key, nil)
end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/main_page_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/main_page_controller_test.exs
index 6ed4d6cb960f..1ea1c334ceb2 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/main_page_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/main_page_controller_test.exs
@@ -62,7 +62,7 @@ defmodule BlockScoutWeb.API.V2.MainPageControllerTest do
end
test "get last 6 txs", %{conn: conn} do
- txs = insert_list(10, :transaction) |> with_block()
+ insert_list(10, :transaction) |> with_block()
auth = build(:auth)
{:ok, user} = UserFromAuth.find_or_create(auth)
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs
index 82d9e08f8481..95584c013bff 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs
@@ -2,6 +2,8 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do
use BlockScoutWeb.ConnCase
alias Explorer.Chain.{Address, Block}
+ alias Explorer.Repo
+ alias Explorer.Tags.AddressTag
setup do
insert(:block)
@@ -27,6 +29,7 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do
item = Enum.at(response["items"], 0)
assert item["type"] == "block"
+ assert item["block_type"] == "block"
assert item["block_number"] == block.number
assert item["block_hash"] == to_string(block.hash)
assert item["url"] =~ to_string(block.hash)
@@ -43,6 +46,39 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do
assert item["block_number"] == block.number
assert item["block_hash"] == to_string(block.hash)
assert item["url"] =~ to_string(block.hash)
+ assert item["timestamp"] == block.timestamp |> to_string() |> String.replace(" ", "T")
+ end
+
+ test "search reorg", %{conn: conn} do
+ block = insert(:block, consensus: false)
+
+ request = get(conn, "/api/v2/search?q=#{block.hash}")
+ assert response = json_response(request, 200)
+
+ assert Enum.count(response["items"]) == 1
+ assert response["next_page_params"] == nil
+
+ item = Enum.at(response["items"], 0)
+
+ assert item["type"] == "block"
+ assert item["block_type"] == "reorg"
+ assert item["block_number"] == block.number
+ assert item["block_hash"] == to_string(block.hash)
+ assert item["url"] =~ to_string(block.hash)
+
+ request = get(conn, "/api/v2/search?q=#{block.number}")
+ assert response = json_response(request, 200)
+
+ assert Enum.count(response["items"]) == 1
+ assert response["next_page_params"] == nil
+
+ item = Enum.at(response["items"], 0)
+
+ assert item["type"] == "block"
+ assert item["block_type"] == "reorg"
+ assert item["block_number"] == block.number
+ assert item["block_hash"] == to_string(block.hash)
+ assert item["url"] =~ to_string(block.hash)
end
test "search address", %{conn: conn} do
@@ -61,6 +97,7 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do
assert item["name"] == name.name
assert item["address"] == Address.checksum(address.hash)
assert item["url"] =~ Address.checksum(address.hash)
+ assert item["is_smart_contract_verified"] == address.verified
end
test "search contract", %{conn: conn} do
@@ -78,6 +115,7 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do
assert item["name"] == contract.name
assert item["address"] == Address.checksum(contract.address_hash)
assert item["url"] =~ Address.checksum(contract.address_hash)
+ assert item["is_smart_contract_verified"] == true
end
test "check pagination", %{conn: conn} do
@@ -126,10 +164,45 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do
assert item["address"] == Address.checksum(token.contract_address_hash)
assert item["token_url"] =~ Address.checksum(token.contract_address_hash)
assert item["address_url"] =~ Address.checksum(token.contract_address_hash)
+ assert item["token_type"] == token.type
+ assert item["is_smart_contract_verified"] == token.contract_address.verified
+ assert item["exchange_rate"] == (token.fiat_value && to_string(token.fiat_value))
+ assert item["total_supply"] == to_string(token.total_supply)
+ assert item["icon_url"] == token.icon_url
+ assert item["is_verified_via_admin_panel"] == token.is_verified_via_admin_panel
+ end
+
+ test "search token by hash", %{conn: conn} do
+ token = insert(:unique_token)
+
+ request = get(conn, "/api/v2/search?q=#{token.contract_address_hash}")
+ assert response = json_response(request, 200)
+
+ assert Enum.count(response["items"]) == 2
+ assert response["next_page_params"] == nil
+
+ item = Enum.at(response["items"], 0)
+
+ assert item["type"] == "token"
+ assert item["name"] == token.name
+ assert item["symbol"] == token.symbol
+ assert item["address"] == Address.checksum(token.contract_address_hash)
+ assert item["token_url"] =~ Address.checksum(token.contract_address_hash)
+ assert item["address_url"] =~ Address.checksum(token.contract_address_hash)
+ assert item["token_type"] == token.type
+ assert item["is_smart_contract_verified"] == token.contract_address.verified
+ assert item["exchange_rate"] == (token.fiat_value && to_string(token.fiat_value))
+ assert item["total_supply"] == to_string(token.total_supply)
+ assert item["icon_url"] == token.icon_url
+ assert item["is_verified_via_admin_panel"] == token.is_verified_via_admin_panel
+
+ item_1 = Enum.at(response["items"], 1)
+
+ assert item_1["type"] == "address"
end
test "search transaction", %{conn: conn} do
- tx = insert(:transaction)
+ tx = insert(:transaction, block_timestamp: nil)
request = get(conn, "/api/v2/search?q=#{tx.hash}")
assert response = json_response(request, 200)
@@ -142,6 +215,62 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do
assert item["type"] == "transaction"
assert item["tx_hash"] == to_string(tx.hash)
assert item["url"] =~ to_string(tx.hash)
+ assert item["timestamp"] == nil
+ end
+
+ test "search transaction with timestamp", %{conn: conn} do
+ tx = :transaction |> insert() |> with_block()
+
+ request = get(conn, "/api/v2/search?q=#{tx.hash}")
+ assert response = json_response(request, 200)
+
+ assert Enum.count(response["items"]) == 1
+ assert response["next_page_params"] == nil
+
+ item = Enum.at(response["items"], 0)
+
+ assert item["type"] == "transaction"
+ assert item["tx_hash"] == to_string(tx.hash)
+ assert item["url"] =~ to_string(tx.hash)
+ assert item["timestamp"] == Repo.preload(tx, [:block]).block.timestamp |> to_string() |> String.replace(" ", "T")
+ end
+
+ test "search tags", %{conn: conn} do
+ tag = insert(:address_to_tag)
+
+ request = get(conn, "/api/v2/search?q=#{tag.tag.display_name}")
+ assert response = json_response(request, 200)
+
+ assert Enum.count(response["items"]) == 1
+ assert response["next_page_params"] == nil
+
+ item = Enum.at(response["items"], 0)
+
+ assert item["type"] == "label"
+ assert item["address"] == Address.checksum(tag.address.hash)
+ assert item["name"] == tag.tag.display_name
+ assert item["url"] =~ Address.checksum(tag.address.hash)
+ assert item["is_smart_contract_verified"] == tag.address.verified
+ end
+
+ test "check that simultaneous search of ", %{conn: conn} do
+ block = insert(:block)
+
+ insert(:smart_contract, name: to_string(block.number))
+ insert(:token, name: to_string(block.number))
+
+ insert(:address_to_tag,
+ tag: %AddressTag{
+ label: "qwerty",
+ display_name: to_string(block.number)
+ }
+ )
+
+ request = get(conn, "/api/v2/search?q=#{block.number}")
+ assert response = json_response(request, 200)
+
+ assert Enum.count(response["items"]) == 4
+ assert response["next_page_params"] == nil
end
end
@@ -242,4 +371,52 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do
%{"redirect" => false, "type" => nil, "parameter" => nil} = json_response(request, 200)
end
end
+
+ describe "/search/quick" do
+ test "check that all categories are in response list", %{conn: conn} do
+ name = "156000"
+
+ tags =
+ for _ <- 0..50 do
+ insert(:address_to_tag, tag: build(:address_tag, display_name: name))
+ end
+
+ contracts = insert_list(50, :smart_contract, name: name)
+ tokens = insert_list(50, :token, name: name)
+ blocks = [insert(:block, number: name, consensus: false), insert(:block, number: name)]
+
+ request = get(conn, "/api/v2/search/quick?q=#{name}")
+ assert response = json_response(request, 200)
+ assert Enum.count(response) == 50
+
+ assert response |> Enum.filter(fn x -> x["type"] == "label" end) |> Enum.map(fn x -> x["address"] end) ==
+ tags |> Enum.reverse() |> Enum.take(16) |> Enum.map(fn tag -> Address.checksum(tag.address.hash) end)
+
+ assert response |> Enum.filter(fn x -> x["type"] == "contract" end) |> Enum.map(fn x -> x["address"] end) ==
+ contracts
+ |> Enum.reverse()
+ |> Enum.take(16)
+ |> Enum.map(fn contract -> Address.checksum(contract.address_hash) end)
+
+ assert response |> Enum.filter(fn x -> x["type"] == "token" end) |> Enum.map(fn x -> x["address"] end) ==
+ tokens
+ |> Enum.reverse()
+ |> Enum.sort_by(fn x -> x.is_verified_via_admin_panel end, :desc)
+ |> Enum.take(16)
+ |> Enum.map(fn token -> Address.checksum(token.contract_address_hash) end)
+
+ block_hashes = response |> Enum.filter(fn x -> x["type"] == "block" end) |> Enum.map(fn x -> x["block_hash"] end)
+
+ assert block_hashes == blocks |> Enum.reverse() |> Enum.map(fn block -> to_string(block.hash) end) ||
+ block_hashes == blocks |> Enum.map(fn block -> to_string(block.hash) end)
+
+ assert response |> Enum.filter(fn x -> x["block_type"] == "block" end) |> Enum.count() == 1
+ assert response |> Enum.filter(fn x -> x["block_type"] == "reorg" end) |> Enum.count() == 1
+ end
+
+ test "returns empty list and don't crash", %{conn: conn} do
+ request = get(conn, "/api/v2/search/quick?q=qwertyuioiuytrewertyuioiuytrertyuio")
+ assert [] = json_response(request, 200)
+ end
+ end
end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs
index deb18a3fd9c3..42477f8fa099 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs
@@ -1,5 +1,5 @@
defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
- use BlockScoutWeb.ConnCase
+ use BlockScoutWeb.ConnCase, async: false
use BlockScoutWeb.ChannelCase, async: false
import Mox
@@ -11,6 +11,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
setup :set_mox_from_context
+ setup :verify_on_exit!
+
describe "/smart-contracts/{address_hash}" do
test "get 404 on non existing SC", %{conn: conn} do
address = build(:address)
@@ -92,7 +94,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"can_be_visualized_via_sol2uml" => false,
"name" => target_contract && target_contract.name,
"compiler_version" => target_contract.compiler_version,
- "optimization_enabled" => if(target_contract.is_vyper_contract, do: nil, else: target_contract.optimization),
+ "optimization_enabled" => target_contract.optimization,
"optimization_runs" => target_contract.optimization_runs,
"evm_version" => target_contract.evm_version,
"verified_at" => target_contract.inserted_at |> to_string() |> String.replace(" ", "T"),
@@ -108,7 +110,9 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"0x6080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029",
"creation_bytecode" =>
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029",
- "abi" => target_contract.abi
+ "abi" => target_contract.abi,
+ "is_verified_via_eth_bytecode_db" => target_contract.verified_via_eth_bytecode_db,
+ "language" => smart_contract_language(target_contract)
}
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(target_contract.address_hash)}")
@@ -178,7 +182,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"can_be_visualized_via_sol2uml" => false,
"name" => target_contract && target_contract.name,
"compiler_version" => target_contract.compiler_version,
- "optimization_enabled" => if(target_contract.is_vyper_contract, do: nil, else: target_contract.optimization),
+ "optimization_enabled" => target_contract.optimization,
"optimization_runs" => target_contract.optimization_runs,
"evm_version" => target_contract.evm_version,
"verified_at" => target_contract.inserted_at |> to_string() |> String.replace(" ", "T"),
@@ -197,7 +201,9 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"0x6080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029",
"creation_bytecode" =>
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029",
- "abi" => target_contract.abi
+ "abi" => target_contract.abi,
+ "is_verified_via_eth_bytecode_db" => target_contract.verified_via_eth_bytecode_db,
+ "language" => smart_contract_language(target_contract)
}
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(target_contract.address_hash)}")
@@ -273,7 +279,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"can_be_visualized_via_sol2uml" => false,
"name" => target_contract && target_contract.name,
"compiler_version" => target_contract.compiler_version,
- "optimization_enabled" => if(target_contract.is_vyper_contract, do: nil, else: target_contract.optimization),
+ "optimization_enabled" => target_contract.optimization,
"optimization_runs" => target_contract.optimization_runs,
"evm_version" => target_contract.evm_version,
"verified_at" => target_contract.inserted_at |> to_string() |> String.replace(" ", "T"),
@@ -289,7 +295,9 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"0x6080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029",
"creation_bytecode" =>
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029",
- "abi" => target_contract.abi
+ "abi" => target_contract.abi,
+ "is_verified_via_eth_bytecode_db" => target_contract.verified_via_eth_bytecode_db,
+ "language" => smart_contract_language(target_contract)
}
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
@@ -297,9 +305,24 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
assert correct_response == response
end
+ end
+
+ describe "/smart-contracts/{address_hash} <> eth_bytecode_db" do
+ setup do
+ old_interval_env = Application.get_env(:explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand)
+
+ :ok
+
+ on_exit(fn ->
+ Application.put_env(:explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand, old_interval_env)
+ end)
+ end
- test "automatically verify contract via Eth Bytecode Interface", %{conn: conn} do
+ test "automatically verify contract", %{conn: conn} do
{:ok, pid} = Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand.start_link([])
+ old_chain_id = Application.get_env(:block_scout_web, :chain_id)
+
+ Application.put_env(:block_scout_web, :chain_id, 5)
bypass = Bypass.open()
eth_bytecode_response = File.read!("./test/support/fixture/smart_contract/eth_bytecode_db_search_response.json")
@@ -308,7 +331,297 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour,
service_url: "http://localhost:#{bypass.port}",
- enabled: true
+ enabled: true,
+ type: "eth_bytecode_db",
+ eth_bytecode_db?: true
+ )
+
+ address = insert(:contract_address)
+
+ insert(:transaction,
+ created_contract_address_hash: address.hash,
+ input:
+ "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029"
+ )
+ |> with_block()
+
+ topic = "addresses:#{address.hash}"
+
+ {:ok, _reply, _socket} =
+ BlockScoutWeb.UserSocketV2
+ |> socket("no_id", %{})
+ |> subscribe_and_join(topic)
+
+ Bypass.expect_once(bypass, "POST", "/api/v2/bytecodes/sources_search_all", fn conn ->
+ Conn.resp(conn, 200, eth_bytecode_response)
+ end)
+
+ request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
+
+ assert_receive %Phoenix.Socket.Message{
+ payload: %{},
+ event: "eth_bytecode_db_lookup_started",
+ topic: ^topic
+ },
+ :timer.seconds(1)
+
+ assert_receive %Phoenix.Socket.Message{
+ payload: %{},
+ event: "smart_contract_was_verified",
+ topic: ^topic
+ },
+ :timer.seconds(1)
+
+ response = json_response(request, 200)
+
+ assert response ==
+ %{
+ "is_self_destructed" => false,
+ "deployed_bytecode" => to_string(address.contract_code),
+ "creation_bytecode" =>
+ "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029"
+ }
+
+ request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
+ assert response = json_response(request, 200)
+ assert %{"is_verified" => true} = response
+ assert %{"is_verified_via_eth_bytecode_db" => true} = response
+ assert %{"is_partially_verified" => true} = response
+ assert %{"is_fully_verified" => false} = response
+
+ Application.put_env(:block_scout_web, :chain_id, old_chain_id)
+ Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, old_env)
+ Bypass.down(bypass)
+ GenServer.stop(pid)
+ end
+
+ test "automatically verify contract using search-all (ethBytecodeDbSources) endpoint", %{conn: conn} do
+ {:ok, pid} = Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand.start_link([])
+ old_chain_id = Application.get_env(:block_scout_web, :chain_id)
+
+ Application.put_env(:block_scout_web, :chain_id, 5)
+
+ bypass = Bypass.open()
+
+ eth_bytecode_response =
+ File.read!("./test/support/fixture/smart_contract/eth_bytecode_db_search_all_local_sources_response.json")
+
+ old_env = Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour)
+
+ Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour,
+ service_url: "http://localhost:#{bypass.port}",
+ enabled: true,
+ type: "eth_bytecode_db",
+ eth_bytecode_db?: true
+ )
+
+ address = insert(:contract_address)
+
+ insert(:transaction,
+ created_contract_address_hash: address.hash,
+ input:
+ "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029"
+ )
+ |> with_block()
+
+ topic = "addresses:#{address.hash}"
+
+ {:ok, _reply, _socket} =
+ BlockScoutWeb.UserSocketV2
+ |> socket("no_id", %{})
+ |> subscribe_and_join(topic)
+
+ Bypass.expect_once(bypass, "POST", "/api/v2/bytecodes/sources_search_all", fn conn ->
+ Conn.resp(conn, 200, eth_bytecode_response)
+ end)
+
+ request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
+
+ assert_receive %Phoenix.Socket.Message{
+ payload: %{},
+ event: "eth_bytecode_db_lookup_started",
+ topic: ^topic
+ },
+ :timer.seconds(1)
+
+ assert_receive %Phoenix.Socket.Message{
+ payload: %{},
+ event: "smart_contract_was_verified",
+ topic: ^topic
+ },
+ :timer.seconds(1)
+
+ response = json_response(request, 200)
+
+ assert response ==
+ %{
+ "is_self_destructed" => false,
+ "deployed_bytecode" => to_string(address.contract_code),
+ "creation_bytecode" =>
+ "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029"
+ }
+
+ request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
+ assert response = json_response(request, 200)
+ assert %{"is_verified" => true} = response
+ assert %{"is_verified_via_eth_bytecode_db" => true} = response
+ assert %{"is_partially_verified" => true} = response
+ assert %{"is_fully_verified" => false} = response
+
+ smart_contract = Jason.decode!(eth_bytecode_response)["ethBytecodeDbSources"] |> List.first()
+ assert response["compiler_settings"] == Jason.decode!(smart_contract["compilerSettings"])
+ assert response["name"] == smart_contract["contractName"]
+ assert response["compiler_version"] == smart_contract["compilerVersion"]
+ assert response["file_path"] == smart_contract["fileName"]
+ assert response["constructor_args"] == smart_contract["constructorArguments"]
+ assert response["abi"] == Jason.decode!(smart_contract["abi"])
+
+ assert response["decoded_constructor_args"] == [
+ [
+ "0xc35dadb65012ec5796536bd9864ed8773abc74c4",
+ %{
+ "internalType" => "address",
+ "name" => "_factory",
+ "type" => "address"
+ }
+ ],
+ [
+ "0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6",
+ %{
+ "internalType" => "address",
+ "name" => "_WETH",
+ "type" => "address"
+ }
+ ]
+ ]
+
+ assert response["source_code"] == smart_contract["sourceFiles"][smart_contract["fileName"]]
+
+ assert response["external_libraries"] == [
+ %{
+ "address_hash" => "0x00000000D41867734BBee4C6863D9255b2b06aC1",
+ "name" => "__CACHE_BREAKER__"
+ }
+ ]
+
+ additional_sources =
+ for file_name <- Map.keys(smart_contract["sourceFiles"]), smart_contract["fileName"] != file_name do
+ %{
+ "source_code" => smart_contract["sourceFiles"][file_name],
+ "file_path" => file_name
+ }
+ end
+
+ assert response["additional_sources"] |> Enum.sort_by(fn x -> x["file_path"] end) ==
+ additional_sources |> Enum.sort_by(fn x -> x["file_path"] end)
+
+ Application.put_env(:block_scout_web, :chain_id, old_chain_id)
+ Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, old_env)
+ Bypass.down(bypass)
+ GenServer.stop(pid)
+ end
+
+ test "automatically verify contract using search-all (sourcifySources) endpoint", %{conn: conn} do
+ {:ok, pid} = Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand.start_link([])
+ old_chain_id = Application.get_env(:block_scout_web, :chain_id)
+
+ Application.put_env(:block_scout_web, :chain_id, 5)
+
+ bypass = Bypass.open()
+
+ eth_bytecode_response =
+ File.read!("./test/support/fixture/smart_contract/eth_bytecode_db_search_all_sourcify_sources_response.json")
+
+ old_env = Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour)
+
+ Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour,
+ service_url: "http://localhost:#{bypass.port}",
+ enabled: true,
+ type: "eth_bytecode_db",
+ eth_bytecode_db?: true
+ )
+
+ address = insert(:contract_address)
+
+ insert(:transaction,
+ created_contract_address_hash: address.hash,
+ input:
+ "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029"
+ )
+ |> with_block()
+
+ topic = "addresses:#{address.hash}"
+
+ {:ok, _reply, _socket} =
+ BlockScoutWeb.UserSocketV2
+ |> socket("no_id", %{})
+ |> subscribe_and_join(topic)
+
+ Bypass.expect_once(bypass, "POST", "/api/v2/bytecodes/sources_search_all", fn conn ->
+ Conn.resp(conn, 200, eth_bytecode_response)
+ end)
+
+ request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
+
+ assert_receive %Phoenix.Socket.Message{
+ payload: %{},
+ event: "eth_bytecode_db_lookup_started",
+ topic: ^topic
+ },
+ :timer.seconds(1)
+
+ assert_receive %Phoenix.Socket.Message{
+ payload: %{},
+ event: "smart_contract_was_verified",
+ topic: ^topic
+ },
+ :timer.seconds(1)
+
+ response = json_response(request, 200)
+
+ assert response ==
+ %{
+ "is_self_destructed" => false,
+ "deployed_bytecode" => to_string(address.contract_code),
+ "creation_bytecode" =>
+ "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029"
+ }
+
+ request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
+ assert response = json_response(request, 200)
+ assert %{"is_verified" => true} = response
+ assert %{"is_verified_via_eth_bytecode_db" => true} = response
+ assert %{"is_verified_via_sourcify" => true} = response
+ assert %{"is_partially_verified" => true} = response
+ assert %{"is_fully_verified" => false} = response
+ assert response["file_path"] == "Test.sol"
+
+ Application.put_env(:block_scout_web, :chain_id, old_chain_id)
+ Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, old_env)
+ Bypass.down(bypass)
+ GenServer.stop(pid)
+ end
+
+ test "automatically verify contract using search-all (sourcifySources with libraries) endpoint", %{conn: conn} do
+ {:ok, pid} = Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand.start_link([])
+ old_chain_id = Application.get_env(:block_scout_web, :chain_id)
+
+ Application.put_env(:block_scout_web, :chain_id, 5)
+
+ bypass = Bypass.open()
+
+ eth_bytecode_response =
+ File.read!(
+ "./test/support/fixture/smart_contract/eth_bytecode_db_search_all_sourcify_sources_with_libs_response.json"
+ )
+
+ old_env = Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour)
+
+ Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour,
+ service_url: "http://localhost:#{bypass.port}",
+ enabled: true,
+ type: "eth_bytecode_db",
+ eth_bytecode_db?: true
)
address = insert(:contract_address)
@@ -327,12 +640,19 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
|> socket("no_id", %{})
|> subscribe_and_join(topic)
- Bypass.expect_once(bypass, "POST", "/api/v2/bytecodes/sources:search", fn conn ->
+ Bypass.expect_once(bypass, "POST", "/api/v2/bytecodes/sources_search_all", fn conn ->
Conn.resp(conn, 200, eth_bytecode_response)
end)
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
+ assert_receive %Phoenix.Socket.Message{
+ payload: %{},
+ event: "eth_bytecode_db_lookup_started",
+ topic: ^topic
+ },
+ :timer.seconds(1)
+
assert_receive %Phoenix.Socket.Message{
payload: %{},
event: "smart_contract_was_verified",
@@ -351,18 +671,60 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
}
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
- assert %{"is_verified" => true} = json_response(request, 200)
+ assert response = json_response(request, 200)
+
+ smart_contract = Jason.decode!(eth_bytecode_response)["sourcifySources"] |> List.first()
+ assert %{"is_verified" => true} = response
+ assert %{"is_verified_via_eth_bytecode_db" => true} = response
+ assert %{"is_verified_via_sourcify" => true} = response
+ assert %{"is_partially_verified" => true} = response
+ assert %{"is_fully_verified" => false} = response
+ assert response["file_path"] == "src/zkbob/ZkBobPool.sol"
+
+ assert response["external_libraries"] == [
+ %{
+ "address_hash" => "0x22DE6B06544Ee5Cd907813a04bcdEd149A2f49D2",
+ "name" => "lib/base58-solidity/contracts/Base58.sol:Base58"
+ },
+ %{
+ "address_hash" => "0x019d3788F00a7087234f3844CB1ceCe1F9982B7A",
+ "name" => "src/libraries/ZkAddress.sol:ZkAddress"
+ }
+ ]
+
+ additional_sources =
+ for file_name <- Map.keys(smart_contract["sourceFiles"]), smart_contract["fileName"] != file_name do
+ %{
+ "source_code" => smart_contract["sourceFiles"][file_name],
+ "file_path" => file_name
+ }
+ end
+
+ assert response["additional_sources"] |> Enum.sort_by(fn x -> x["file_path"] end) ==
+ additional_sources |> Enum.sort_by(fn x -> x["file_path"] end)
+
+ Application.put_env(:block_scout_web, :chain_id, old_chain_id)
Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, old_env)
Bypass.down(bypass)
GenServer.stop(pid)
end
- test "check fetch interval for LookUpSmartContractSourcesOnDemand", %{conn: conn} do
+ test "check fetch interval for LookUpSmartContractSourcesOnDemand and use sources:search endpoint since chain_id is unset",
+ %{conn: conn} do
{:ok, pid} = Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand.start_link([])
+ old_chain_id = Application.get_env(:block_scout_web, :chain_id)
+
+ Application.put_env(:block_scout_web, :chain_id, nil)
bypass = Bypass.open()
address = insert(:contract_address)
+ topic = "addresses:#{address.hash}"
+
+ {:ok, _reply, _socket} =
+ BlockScoutWeb.UserSocketV2
+ |> socket("no_id", %{})
+ |> subscribe_and_join(topic)
insert(:transaction,
created_contract_address_hash: address.hash,
@@ -375,41 +737,107 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour,
service_url: "http://localhost:#{bypass.port}",
- enabled: true
+ enabled: true,
+ type: "eth_bytecode_db",
+ eth_bytecode_db?: true
)
old_interval_env = Application.get_env(:explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand)
Application.put_env(:explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand, fetch_interval: 0)
- Bypass.expect_once(bypass, "POST", "/api/v2/bytecodes/sources:search", fn conn ->
+ Bypass.expect_once(bypass, "POST", "/api/v2/bytecodes/sources_search", fn conn ->
Conn.resp(conn, 200, "{\"sources\": []}")
end)
_request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
+ assert_receive %Phoenix.Socket.Message{
+ payload: %{},
+ event: "eth_bytecode_db_lookup_started",
+ topic: ^topic
+ },
+ :timer.seconds(1)
+
+ assert_receive %Phoenix.Socket.Message{
+ payload: %{},
+ event: "smart_contract_was_not_verified",
+ topic: ^topic
+ },
+ :timer.seconds(1)
+
:timer.sleep(10)
- Bypass.expect_once(bypass, "POST", "/api/v2/bytecodes/sources:search", fn conn ->
+ Bypass.expect_once(bypass, "POST", "/api/v2/bytecodes/sources_search", fn conn ->
Conn.resp(conn, 200, "{\"sources\": []}")
end)
_request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
+ assert_receive %Phoenix.Socket.Message{
+ payload: %{},
+ event: "eth_bytecode_db_lookup_started",
+ topic: ^topic
+ },
+ :timer.seconds(1)
+
+ assert_receive %Phoenix.Socket.Message{
+ payload: %{},
+ event: "smart_contract_was_not_verified",
+ topic: ^topic
+ },
+ :timer.seconds(1)
+
:timer.sleep(10)
- Bypass.expect_once(bypass, "POST", "/api/v2/bytecodes/sources:search", fn conn ->
+ Bypass.expect_once(bypass, "POST", "/api/v2/bytecodes/sources_search", fn conn ->
Conn.resp(conn, 200, "{\"sources\": []}")
end)
_request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
+ assert_receive %Phoenix.Socket.Message{
+ payload: %{},
+ event: "eth_bytecode_db_lookup_started",
+ topic: ^topic
+ },
+ :timer.seconds(1)
+
+ assert_receive %Phoenix.Socket.Message{
+ payload: %{},
+ event: "smart_contract_was_not_verified",
+ topic: ^topic
+ },
+ :timer.seconds(1)
+
:timer.sleep(10)
Application.put_env(:explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand, fetch_interval: 10000)
_request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
+ refute_receive %Phoenix.Socket.Message{
+ payload: %{},
+ event: "eth_bytecode_db_lookup_started",
+ topic: ^topic
+ },
+ :timer.seconds(1)
+
+ refute_receive %Phoenix.Socket.Message{
+ payload: %{},
+ event: "smart_contract_was_not_verified",
+ topic: ^topic
+ },
+ :timer.seconds(1)
+
+ refute_receive %Phoenix.Socket.Message{
+ payload: %{},
+ event: "smart_contract_was_verified",
+ topic: ^topic
+ },
+ :timer.seconds(1)
+
+ Application.put_env(:block_scout_web, :chain_id, old_chain_id)
Application.put_env(:explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand, old_interval_env)
Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, old_env)
Bypass.down(bypass)
@@ -462,24 +890,84 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"name" => "disableWhitelist",
"inputs" => [%{"type" => "bool", "name" => "disable", "internalType" => "bool"}]
},
- %{"type" => "fallback"}
+ %{"type" => "fallback"},
+ %{"type" => "receive"},
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "outputs" => [
+ %{
+ "type" => "tuple",
+ "name" => "",
+ "internalType" => "struct Storage.TransactionReceipt",
+ "components" => [
+ %{"type" => "bytes32", "name" => "txHash", "internalType" => "bytes32"},
+ %{"type" => "uint256", "name" => "blockNumber", "internalType" => "uint256"},
+ %{"type" => "bytes32", "name" => "blockHash", "internalType" => "bytes32"},
+ %{"type" => "uint256", "name" => "transactionIndex", "internalType" => "uint256"},
+ %{"type" => "address", "name" => "from", "internalType" => "address"},
+ %{"type" => "address", "name" => "to", "internalType" => "address"},
+ %{"type" => "uint256", "name" => "gasUsed", "internalType" => "uint256"},
+ %{"type" => "bool", "name" => "status", "internalType" => "bool"},
+ %{
+ "type" => "tuple[]",
+ "name" => "logs",
+ "internalType" => "struct Storage.Log[]",
+ "components" => [
+ %{"type" => "address", "name" => "from", "internalType" => "address"},
+ %{"type" => "bytes32[]", "name" => "topics", "internalType" => "bytes32[]"},
+ %{"type" => "bytes", "name" => "data", "internalType" => "bytes"}
+ ]
+ }
+ ]
+ }
+ ],
+ "name" => "retrieve",
+ "inputs" => []
+ }
]
target_contract = insert(:smart_contract, abi: abi)
- blockchain_eth_call_mock()
+ blockchain_eth_call_mock()
+
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn [
+ %{
+ id: id,
+ method: "eth_call",
+ params: [%{to: _address_hash, from: "0xBb36c792B9B45Aaf8b848A1392B0d6559202729E"}, _]
+ }
+ ],
+ _opts ->
+ {:ok,
+ [
+ %{
+ id: id,
+ jsonrpc: "2.0",
+ result:
+ "0x0000000000000000000000000000000000000000000000000000000000000020fe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab200000000000000000000000000000000000000000000000000000000000003e8fe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab2000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000001e0f30000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003307830000000000000000000000000000000000000000000000000000000000030783030313132323333000000000000000000000000000000000000000000003078303031313232333331323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3078303030303132333132330000000000000000000000000000000000000000000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003307830000000000000000000000000000000000000000000000000000000000030783030313132323333000000000000000000000000000000000000000000003078303031313232333331323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3078303030303132333132330000000000000000000000000000000000000000000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003307830000000000000000000000000000000000000000000000000000000000030783030313132323333000000000000000000000000000000000000000000003078303031313232333331323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3078303030303132333132330000000000000000000000000000000000000000"
+ }
+ ]}
+ end
+ )
+
+ request =
+ get(conn, "/api/v2/smart-contracts/#{target_contract.address_hash}/methods-read", %{
+ "from" => "0xBb36c792B9B45Aaf8b848A1392B0d6559202729E"
+ })
- request = get(conn, "/api/v2/smart-contracts/#{target_contract.address_hash}/methods-read")
assert response = json_response(request, 200)
assert %{
"type" => "function",
"stateMutability" => "view",
+ "names" => ["address"],
"outputs" => [
%{
"type" => "address",
- "name" => "",
- "internalType" => "address",
"value" => "0xfffffffffffffffffffffffffffffffffffffffe"
}
],
@@ -497,7 +985,77 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"method_id" => "c683630d"
} in response
- assert %{"type" => "fallback"} in response
+ assert %{
+ "inputs" => [],
+ "method_id" => "2e64cec1",
+ "name" => "retrieve",
+ "names" => [
+ [
+ "struct Storage.TransactionReceipt",
+ [
+ "txHash",
+ "blockNumber",
+ "blockHash",
+ "transactionIndex",
+ "from",
+ "to",
+ "gasUsed",
+ "status",
+ ["logs", ["from", "topics", "data"]]
+ ]
+ ]
+ ],
+ "outputs" => [
+ %{
+ "type" =>
+ "tuple[bytes32,uint256,bytes32,uint256,address,address,uint256,bool,tuple[address,bytes32[],bytes][]]",
+ "value" => [
+ "0xfe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab2",
+ 1000,
+ "0xfe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab2",
+ 10,
+ "0xbb36c792b9b45aaf8b848a1392b0d6559202729e",
+ "0xbb36c792b9b45aaf8b848a1392b0d6559202729e",
+ 123_123,
+ true,
+ [
+ [
+ "0xbb36c792b9b45aaf8b848a1392b0d6559202729e",
+ [
+ "0x3078300000000000000000000000000000000000000000000000000000000000",
+ "0x3078303031313232333300000000000000000000000000000000000000000000",
+ "0x3078303031313232333331323300000000000000000000000000000000000000"
+ ],
+ "0x307830303030313233313233"
+ ],
+ [
+ "0xbb36c792b9b45aaf8b848a1392b0d6559202729e",
+ [
+ "0x3078300000000000000000000000000000000000000000000000000000000000",
+ "0x3078303031313232333300000000000000000000000000000000000000000000",
+ "0x3078303031313232333331323300000000000000000000000000000000000000"
+ ],
+ "0x307830303030313233313233"
+ ],
+ [
+ "0xbb36c792b9b45aaf8b848a1392b0d6559202729e",
+ [
+ "0x3078300000000000000000000000000000000000000000000000000000000000",
+ "0x3078303031313232333300000000000000000000000000000000000000000000",
+ "0x3078303031313232333331323300000000000000000000000000000000000000"
+ ],
+ "0x307830303030313233313233"
+ ]
+ ]
+ ]
+ }
+ ],
+ "stateMutability" => "view",
+ "type" => "function"
+ } in response
+
+ refute %{"type" => "fallback"} in response
+ refute %{"type" => "receive"} in response
end
test "get array of addresses within read-methods", %{conn: conn} do
@@ -544,10 +1102,10 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"type" => "function",
"stateMutability" => "view",
"payable" => false,
+ "names" => [nil],
"outputs" => [
%{
"type" => "address[]",
- "name" => "",
"value" => [
"0x64631b5d259ead889e8b06d12c8b74742804e5f1",
"0x234fe7224ce480ca97d01897311b8c3d35162f86",
@@ -562,6 +1120,148 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"method_id" => Base.encode16(id, case: :lower)
} in response
end
+
+ test "get correct bytes value 1", %{conn: conn} do
+ abi = [
+ %{
+ "inputs" => [],
+ "name" => "all_messages_hash",
+ "outputs" => [
+ %{
+ "internalType" => "bytes32",
+ "name" => "",
+ "type" => "bytes32"
+ }
+ ],
+ "stateMutability" => "view",
+ "type" => "function"
+ }
+ ]
+
+ id_1 =
+ abi
+ |> ABI.parse_specification()
+ |> Enum.at(0)
+ |> Map.fetch!(:method_id)
+
+ target_contract = insert(:smart_contract, abi: abi)
+ address_hash_string = to_string(target_contract.address_hash)
+
+ EthereumJSONRPC.Mox
+ |> expect(
+ :json_rpc,
+ fn [
+ %{
+ id: id,
+ method: "eth_call",
+ params: [
+ %{data: "0x1dd69d06", to: ^address_hash_string},
+ "latest"
+ ]
+ }
+ ],
+ _opts ->
+ {:ok,
+ [
+ %{
+ id: id,
+ jsonrpc: "2.0",
+ result: "0x0000000000000000000000000000000000000000000000000000000000000000"
+ }
+ ]}
+ end
+ )
+
+ request = get(conn, "/api/v2/smart-contracts/#{target_contract.address_hash}/methods-read")
+ assert response = json_response(request, 200)
+
+ assert %{
+ "inputs" => [],
+ "name" => "all_messages_hash",
+ "outputs" => [
+ %{
+ "value" => "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "type" => "bytes32"
+ }
+ ],
+ "stateMutability" => "view",
+ "type" => "function",
+ "method_id" => Base.encode16(id_1, case: :lower),
+ "names" => ["bytes32"]
+ } in response
+ end
+
+ test "get correct bytes value 2", %{conn: conn} do
+ abi = [
+ %{
+ "inputs" => [],
+ "name" => "FRAUD_STRING",
+ "outputs" => [
+ %{
+ "internalType" => "bytes",
+ "name" => "",
+ "type" => "bytes"
+ }
+ ],
+ "stateMutability" => "view",
+ "type" => "function"
+ }
+ ]
+
+ id_2 =
+ abi
+ |> ABI.parse_specification()
+ |> Enum.at(0)
+ |> Map.fetch!(:method_id)
+
+ target_contract = insert(:smart_contract, abi: abi)
+ address_hash_string = to_string(target_contract.address_hash)
+
+ EthereumJSONRPC.Mox
+ |> expect(
+ :json_rpc,
+ fn [
+ %{
+ id: id,
+ method: "eth_call",
+ params: [
+ %{data: "0x46b2eb9b", to: ^address_hash_string},
+ "latest"
+ ]
+ }
+ ],
+ _opts ->
+ {:ok,
+ [
+ %{
+ id: id,
+ jsonrpc: "2.0",
+ result:
+ "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000322d2d5468697320697320612062616420737472696e672e204e6f626f64792073617973207468697320737472696e672e2d2d0000000000000000000000000000"
+ }
+ ]}
+ end
+ )
+
+ request = get(conn, "/api/v2/smart-contracts/#{target_contract.address_hash}/methods-read")
+ assert response = json_response(request, 200)
+
+ assert %{
+ "inputs" => [],
+ "name" => "FRAUD_STRING",
+ "outputs" => [
+ %{
+ "value" =>
+ "0x2d2d5468697320697320612062616420737472696e672e204e6f626f64792073617973207468697320737472696e672e2d2d",
+ "type" => "bytes"
+ }
+ ],
+ "stateMutability" => "view",
+ "type" => "function",
+ "method_id" => Base.encode16(id_2, case: :lower),
+ "names" => ["bytes"]
+ } in response
+ end
end
describe "/smart-contracts/{address_hash}/query-read-method" do
@@ -666,6 +1366,146 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
} == response
end
+ test "query complex response", %{conn: conn} do
+ abi = [
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "outputs" => [
+ %{
+ "type" => "tuple",
+ "name" => "",
+ "internalType" => "struct Storage.TransactionReceipt",
+ "components" => [
+ %{"type" => "bytes32", "name" => "txHash", "internalType" => "bytes32"},
+ %{"type" => "uint256", "name" => "blockNumber", "internalType" => "uint256"},
+ %{"type" => "bytes32", "name" => "blockHash", "internalType" => "bytes32"},
+ %{"type" => "uint256", "name" => "transactionIndex", "internalType" => "uint256"},
+ %{"type" => "address", "name" => "from", "internalType" => "address"},
+ %{"type" => "address", "name" => "to", "internalType" => "address"},
+ %{"type" => "uint256", "name" => "gasUsed", "internalType" => "uint256"},
+ %{"type" => "bool", "name" => "status", "internalType" => "bool"},
+ %{
+ "type" => "tuple[]",
+ "name" => "logs",
+ "internalType" => "struct Storage.Log[]",
+ "components" => [
+ %{"type" => "address", "name" => "from", "internalType" => "address"},
+ %{"type" => "bytes32[]", "name" => "topics", "internalType" => "bytes32[]"},
+ %{"type" => "bytes", "name" => "data", "internalType" => "bytes"}
+ ]
+ }
+ ]
+ }
+ ],
+ "name" => "retrieve",
+ "inputs" => []
+ }
+ ]
+
+ target_contract = insert(:smart_contract, abi: abi)
+
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn [
+ %{
+ id: id,
+ method: "eth_call",
+ params: [%{to: _address_hash, from: "0xBb36c792B9B45Aaf8b848A1392B0d6559202729E"}, _]
+ }
+ ],
+ _opts ->
+ {:ok,
+ [
+ %{
+ id: id,
+ jsonrpc: "2.0",
+ result:
+ "0x0000000000000000000000000000000000000000000000000000000000000020fe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab200000000000000000000000000000000000000000000000000000000000003e8fe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab2000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000001e0f30000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003307830000000000000000000000000000000000000000000000000000000000030783030313132323333000000000000000000000000000000000000000000003078303031313232333331323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3078303030303132333132330000000000000000000000000000000000000000000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003307830000000000000000000000000000000000000000000000000000000000030783030313132323333000000000000000000000000000000000000000000003078303031313232333331323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3078303030303132333132330000000000000000000000000000000000000000000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003307830000000000000000000000000000000000000000000000000000000000030783030313132323333000000000000000000000000000000000000000000003078303031313232333331323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3078303030303132333132330000000000000000000000000000000000000000"
+ }
+ ]}
+ end
+ )
+
+ request =
+ post(conn, "/api/v2/smart-contracts/#{target_contract.address_hash}/query-read-method", %{
+ "contract_type" => "regular",
+ "args" => [],
+ "method_id" => "2e64cec1",
+ "from" => "0xBb36c792B9B45Aaf8b848A1392B0d6559202729E"
+ })
+
+ assert response = json_response(request, 200)
+
+ assert %{
+ "is_error" => false,
+ "result" => %{
+ "names" => [
+ [
+ "struct Storage.TransactionReceipt",
+ [
+ "txHash",
+ "blockNumber",
+ "blockHash",
+ "transactionIndex",
+ "from",
+ "to",
+ "gasUsed",
+ "status",
+ ["logs", ["from", "topics", "data"]]
+ ]
+ ]
+ ],
+ "output" => [
+ %{
+ "type" =>
+ "tuple[bytes32,uint256,bytes32,uint256,address,address,uint256,bool,tuple[address,bytes32[],bytes][]]",
+ "value" => [
+ "0xfe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab2",
+ 1000,
+ "0xfe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab2",
+ 10,
+ "0xbb36c792b9b45aaf8b848a1392b0d6559202729e",
+ "0xbb36c792b9b45aaf8b848a1392b0d6559202729e",
+ 123_123,
+ true,
+ [
+ [
+ "0xbb36c792b9b45aaf8b848a1392b0d6559202729e",
+ [
+ "0x3078300000000000000000000000000000000000000000000000000000000000",
+ "0x3078303031313232333300000000000000000000000000000000000000000000",
+ "0x3078303031313232333331323300000000000000000000000000000000000000"
+ ],
+ "0x307830303030313233313233"
+ ],
+ [
+ "0xbb36c792b9b45aaf8b848a1392b0d6559202729e",
+ [
+ "0x3078300000000000000000000000000000000000000000000000000000000000",
+ "0x3078303031313232333300000000000000000000000000000000000000000000",
+ "0x3078303031313232333331323300000000000000000000000000000000000000"
+ ],
+ "0x307830303030313233313233"
+ ],
+ [
+ "0xbb36c792b9b45aaf8b848a1392b0d6559202729e",
+ [
+ "0x3078300000000000000000000000000000000000000000000000000000000000",
+ "0x3078303031313232333300000000000000000000000000000000000000000000",
+ "0x3078303031313232333331323300000000000000000000000000000000000000"
+ ],
+ "0x307830303030313233313233"
+ ]
+ ]
+ ]
+ }
+ ]
+ }
+ } == response
+ end
+
test "query-read-method with nonexistent method_id", %{conn: conn} do
abi = [
%{
@@ -925,6 +1765,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
assert [
%{
+ "method_id" => "49ba1b49",
"type" => "function",
"stateMutability" => "nonpayable",
"outputs" => [],
@@ -991,7 +1832,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"stateMutability" => "nonpayable",
"outputs" => [],
"name" => "disableWhitelist",
- "inputs" => [%{"type" => "bool", "name" => "disable", "internalType" => "bool"}]
+ "inputs" => [%{"type" => "bool", "name" => "disable", "internalType" => "bool"}],
+ "method_id" => "49ba1b49"
}
] == response
end
@@ -1041,11 +1883,10 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
assert %{
"type" => "function",
"stateMutability" => "view",
+ "names" => ["address"],
"outputs" => [
%{
"type" => "address",
- "name" => "",
- "internalType" => "address",
"value" => "0xfffffffffffffffffffffffffffffffffffffffe"
}
],
@@ -1185,18 +2026,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
target_contract = insert(:smart_contract, abi: abi)
- expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
- id: 0,
- method: "eth_getStorageAt",
- params: [
- _,
- "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
- "latest"
- ]
- },
- _options ->
- {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"}
- end)
+ mock_logic_storage_pointer_request(target_contract.address_hash)
expect(
EthereumJSONRPC.Mox,
@@ -1220,11 +2050,10 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
assert %{
"type" => "function",
"stateMutability" => "view",
+ "names" => ["address"],
"outputs" => [
%{
"type" => "address",
- "name" => "",
- "internalType" => "address",
"value" => "0xfffffffffffffffffffffffffffffffffffffffe"
}
],
@@ -1296,18 +2125,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
target_contract = insert(:smart_contract, abi: abi)
- expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
- id: 0,
- method: "eth_getStorageAt",
- params: [
- _,
- "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
- "latest"
- ]
- },
- _options ->
- {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"}
- end)
+ mock_logic_storage_pointer_request(target_contract.address_hash)
expect(
EthereumJSONRPC.Mox,
@@ -1381,18 +2199,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
target_contract = insert(:smart_contract, abi: abi)
- expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
- id: 0,
- method: "eth_getStorageAt",
- params: [
- _,
- "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
- "latest"
- ]
- },
- _options ->
- {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"}
- end)
+ mock_logic_storage_pointer_request(target_contract.address_hash)
expect(
EthereumJSONRPC.Mox,
@@ -1450,18 +2257,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
target_contract = insert(:smart_contract, abi: abi)
- expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
- id: 0,
- method: "eth_getStorageAt",
- params: [
- _,
- "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
- "latest"
- ]
- },
- _options ->
- {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"}
- end)
+ mock_logic_storage_pointer_request(target_contract.address_hash)
expect(
EthereumJSONRPC.Mox,
@@ -1518,18 +2314,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
target_contract = insert(:smart_contract, abi: abi)
- expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
- id: 0,
- method: "eth_getStorageAt",
- params: [
- _,
- "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
- "latest"
- ]
- },
- _options ->
- {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"}
- end)
+ mock_logic_storage_pointer_request(target_contract.address_hash)
expect(
EthereumJSONRPC.Mox,
@@ -1610,18 +2395,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
target_contract = insert(:smart_contract, abi: abi)
- expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
- id: 0,
- method: "eth_getStorageAt",
- params: [
- _,
- "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
- "latest"
- ]
- },
- _options ->
- {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"}
- end)
+ mock_logic_storage_pointer_request(target_contract.address_hash)
contract = insert(:smart_contract)
@@ -1634,7 +2408,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"stateMutability" => "nonpayable",
"outputs" => [],
"name" => "disableWhitelist",
- "inputs" => [%{"type" => "bool", "name" => "disable", "internalType" => "bool"}]
+ "inputs" => [%{"type" => "bool", "name" => "disable", "internalType" => "bool"}],
+ "method_id" => "49ba1b49"
}
] == response
end
@@ -1653,6 +2428,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
assert %{"items" => [sc], "next_page_params" => nil} = json_response(request, 200)
compare_item(smart_contract, sc)
+ assert sc["address"]["is_verified"] == true
+ assert sc["address"]["is_contract"] == true
end
test "check pagination", %{conn: conn} do
@@ -1670,6 +2447,107 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
check_paginated_response(response, response_2nd_page, smart_contracts)
end
+
+ test "ignores wrong ordering params", %{conn: conn} do
+ smart_contracts =
+ for _ <- 0..50 do
+ insert(:smart_contract)
+ end
+
+ ordering_params = %{"sort" => "foo", "order" => "bar"}
+
+ request = get(conn, "/api/v2/smart-contracts", ordering_params)
+ assert response = json_response(request, 200)
+
+ request_2nd_page =
+ get(conn, "/api/v2/smart-contracts", ordering_params |> Map.merge(response["next_page_params"]))
+
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ check_paginated_response(response, response_2nd_page, smart_contracts)
+ end
+
+ test "can order by balance ascending", %{conn: conn} do
+ smart_contracts =
+ for i <- 0..50 do
+ address = insert(:address, fetched_coin_balance: i)
+ insert(:smart_contract, address_hash: address.hash, address: address)
+ end
+ |> Enum.reverse()
+
+ ordering_params = %{"sort" => "balance", "order" => "asc"}
+
+ request = get(conn, "/api/v2/smart-contracts", ordering_params)
+ assert response = json_response(request, 200)
+
+ request_2nd_page =
+ get(conn, "/api/v2/smart-contracts", ordering_params |> Map.merge(response["next_page_params"]))
+
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ check_paginated_response(response, response_2nd_page, smart_contracts)
+ end
+
+ test "can order by balance descending", %{conn: conn} do
+ smart_contracts =
+ for i <- 0..50 do
+ address = insert(:address, fetched_coin_balance: i)
+ insert(:smart_contract, address_hash: address.hash, address: address)
+ end
+
+ ordering_params = %{"sort" => "balance", "order" => "desc"}
+
+ request = get(conn, "/api/v2/smart-contracts", ordering_params)
+ assert response = json_response(request, 200)
+
+ request_2nd_page =
+ get(conn, "/api/v2/smart-contracts", ordering_params |> Map.merge(response["next_page_params"]))
+
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ check_paginated_response(response, response_2nd_page, smart_contracts)
+ end
+
+ test "can order by transaction count ascending", %{conn: conn} do
+ smart_contracts =
+ for i <- 0..50 do
+ address = insert(:address, transactions_count: i)
+ insert(:smart_contract, address_hash: address.hash, address: address)
+ end
+ |> Enum.reverse()
+
+ ordering_params = %{"sort" => "txs_count", "order" => "asc"}
+
+ request = get(conn, "/api/v2/smart-contracts", ordering_params)
+ assert response = json_response(request, 200)
+
+ request_2nd_page =
+ get(conn, "/api/v2/smart-contracts", ordering_params |> Map.merge(response["next_page_params"]))
+
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ check_paginated_response(response, response_2nd_page, smart_contracts)
+ end
+
+ test "can order by transaction count descending", %{conn: conn} do
+ smart_contracts =
+ for i <- 0..50 do
+ address = insert(:address, transactions_count: i)
+ insert(:smart_contract, address_hash: address.hash, address: address)
+ end
+
+ ordering_params = %{"sort" => "txs_count", "order" => "desc"}
+
+ request = get(conn, "/api/v2/smart-contracts", ordering_params)
+ assert response = json_response(request, 200)
+
+ request_2nd_page =
+ get(conn, "/api/v2/smart-contracts", ordering_params |> Map.merge(response["next_page_params"]))
+
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ check_paginated_response(response, response_2nd_page, smart_contracts)
+ end
end
describe "/smart-contracts/counters" do
@@ -1688,8 +2566,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
defp compare_item(%SmartContract{} = smart_contract, json) do
assert smart_contract.compiler_version == json["compiler_version"]
- assert if(smart_contract.is_vyper_contract, do: nil, else: smart_contract.optimization) ==
- json["optimization_enabled"]
+ assert smart_contract.optimization == json["optimization_enabled"]
assert json["language"] == if(smart_contract.is_vyper_contract, do: "vyper", else: "solidity")
assert json["verified_at"]
@@ -1724,4 +2601,32 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
end
)
end
+
+ defp smart_contract_language(smart_contract) do
+ cond do
+ smart_contract.is_vyper_contract ->
+ "vyper"
+
+ is_nil(smart_contract.abi) ->
+ "yul"
+
+ true ->
+ "solidity"
+ end
+ end
+
+ defp mock_logic_storage_pointer_request(address_hash) do
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x000000000000000000000000#{address_hash |> to_string() |> String.replace("0x", "")}"}
+ end)
+ end
end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/stats_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/stats_controller_test.exs
index 1c1659791fd5..85ac41e1d14d 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/stats_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/stats_controller_test.exs
@@ -42,7 +42,6 @@ defmodule BlockScoutWeb.API.V2.StatsControllerTest do
assert response = json_response(request, 200)
assert response["chart_data"] == []
- assert response["available_supply"] == 0
end
end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs
index ddb250733a3c..daefeaa8cb85 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs
@@ -390,31 +390,441 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do
check_paginated_response(response, response_2nd_page, token_balances)
end
+
+ test "check pagination with the same values", %{conn: conn} do
+ token = insert(:token)
+
+ token_balances =
+ for _ <- 0..50 do
+ insert(
+ :address_current_token_balance,
+ token_contract_address_hash: token.contract_address_hash,
+ value: 1000
+ )
+ end
+ |> Enum.sort_by(fn x -> x.address_hash end, :asc)
+
+ request = get(conn, "/api/v2/tokens/#{token.contract_address.hash}/holders")
+ assert response = json_response(request, 200)
+
+ request_2nd_page =
+ get(conn, "/api/v2/tokens/#{token.contract_address.hash}/holders", response["next_page_params"])
+
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ check_paginated_response(response, response_2nd_page, token_balances)
+ end
end
describe "/tokens" do
+ defp check_tokens_pagination(tokens, conn, additional_params \\ %{}) do
+ request = get(conn, "/api/v2/tokens", additional_params)
+ assert response = json_response(request, 200)
+ request_2nd_page = get(conn, "/api/v2/tokens", additional_params |> Map.merge(response["next_page_params"]))
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+ check_paginated_response(response, response_2nd_page, tokens)
+
+ # by fiat_value
+ tokens_ordered_by_fiat_value = Enum.sort(tokens, &(Decimal.compare(&1.fiat_value, &2.fiat_value) in [:eq, :lt]))
+
+ request_ordered_by_fiat_value =
+ get(conn, "/api/v2/tokens", additional_params |> Map.merge(%{"sort" => "fiat_value", "order" => "desc"}))
+
+ assert response_ordered_by_fiat_value = json_response(request_ordered_by_fiat_value, 200)
+
+ request_ordered_by_fiat_value_2nd_page =
+ get(
+ conn,
+ "/api/v2/tokens",
+ additional_params
+ |> Map.merge(%{"sort" => "fiat_value", "order" => "desc"})
+ |> Map.merge(response_ordered_by_fiat_value["next_page_params"])
+ )
+
+ assert response_ordered_by_fiat_value_2nd_page = json_response(request_ordered_by_fiat_value_2nd_page, 200)
+
+ check_paginated_response(
+ response_ordered_by_fiat_value,
+ response_ordered_by_fiat_value_2nd_page,
+ tokens_ordered_by_fiat_value
+ )
+
+ tokens_ordered_by_fiat_value_asc =
+ Enum.sort(tokens, &(Decimal.compare(&1.fiat_value, &2.fiat_value) in [:eq, :gt]))
+
+ request_ordered_by_fiat_value_asc =
+ get(conn, "/api/v2/tokens", additional_params |> Map.merge(%{"sort" => "fiat_value", "order" => "asc"}))
+
+ assert response_ordered_by_fiat_value_asc = json_response(request_ordered_by_fiat_value_asc, 200)
+
+ request_ordered_by_fiat_value_asc_2nd_page =
+ get(
+ conn,
+ "/api/v2/tokens",
+ additional_params
+ |> Map.merge(%{"sort" => "fiat_value", "order" => "asc"})
+ |> Map.merge(response_ordered_by_fiat_value_asc["next_page_params"])
+ )
+
+ assert response_ordered_by_fiat_value_asc_2nd_page =
+ json_response(request_ordered_by_fiat_value_asc_2nd_page, 200)
+
+ check_paginated_response(
+ response_ordered_by_fiat_value_asc,
+ response_ordered_by_fiat_value_asc_2nd_page,
+ tokens_ordered_by_fiat_value_asc
+ )
+
+ # by holders
+ tokens_ordered_by_holders = Enum.sort(tokens, &(&1.holder_count <= &2.holder_count))
+
+ request_ordered_by_holders =
+ get(conn, "/api/v2/tokens", additional_params |> Map.merge(%{"sort" => "holder_count", "order" => "desc"}))
+
+ assert response_ordered_by_holders = json_response(request_ordered_by_holders, 200)
+
+ request_ordered_by_holders_2nd_page =
+ get(
+ conn,
+ "/api/v2/tokens",
+ additional_params
+ |> Map.merge(%{"sort" => "holder_count", "order" => "desc"})
+ |> Map.merge(response_ordered_by_holders["next_page_params"])
+ )
+
+ assert response_ordered_by_holders_2nd_page = json_response(request_ordered_by_holders_2nd_page, 200)
+
+ check_paginated_response(
+ response_ordered_by_holders,
+ response_ordered_by_holders_2nd_page,
+ tokens_ordered_by_holders
+ )
+
+ tokens_ordered_by_holders_asc = Enum.sort(tokens, &(&1.holder_count >= &2.holder_count))
+
+ request_ordered_by_holders_asc =
+ get(conn, "/api/v2/tokens", additional_params |> Map.merge(%{"sort" => "holder_count", "order" => "asc"}))
+
+ assert response_ordered_by_holders_asc = json_response(request_ordered_by_holders_asc, 200)
+
+ request_ordered_by_holders_asc_2nd_page =
+ get(
+ conn,
+ "/api/v2/tokens",
+ additional_params
+ |> Map.merge(%{"sort" => "holder_count", "order" => "asc"})
+ |> Map.merge(response_ordered_by_holders_asc["next_page_params"])
+ )
+
+ assert response_ordered_by_holders_asc_2nd_page = json_response(request_ordered_by_holders_asc_2nd_page, 200)
+
+ check_paginated_response(
+ response_ordered_by_holders_asc,
+ response_ordered_by_holders_asc_2nd_page,
+ tokens_ordered_by_holders_asc
+ )
+
+ # by circulating_market_cap
+ tokens_ordered_by_circulating_market_cap =
+ Enum.sort(tokens, &(&1.circulating_market_cap <= &2.circulating_market_cap))
+
+ request_ordered_by_circulating_market_cap =
+ get(
+ conn,
+ "/api/v2/tokens",
+ additional_params |> Map.merge(%{"sort" => "circulating_market_cap", "order" => "desc"})
+ )
+
+ assert response_ordered_by_circulating_market_cap = json_response(request_ordered_by_circulating_market_cap, 200)
+
+ request_ordered_by_circulating_market_cap_2nd_page =
+ get(
+ conn,
+ "/api/v2/tokens",
+ additional_params
+ |> Map.merge(%{"sort" => "circulating_market_cap", "order" => "desc"})
+ |> Map.merge(response_ordered_by_circulating_market_cap["next_page_params"])
+ )
+
+ assert response_ordered_by_circulating_market_cap_2nd_page =
+ json_response(request_ordered_by_circulating_market_cap_2nd_page, 200)
+
+ check_paginated_response(
+ response_ordered_by_circulating_market_cap,
+ response_ordered_by_circulating_market_cap_2nd_page,
+ tokens_ordered_by_circulating_market_cap
+ )
+
+ tokens_ordered_by_circulating_market_cap_asc =
+ Enum.sort(tokens, &(&1.circulating_market_cap >= &2.circulating_market_cap))
+
+ request_ordered_by_circulating_market_cap_asc =
+ get(
+ conn,
+ "/api/v2/tokens",
+ additional_params |> Map.merge(%{"sort" => "circulating_market_cap", "order" => "asc"})
+ )
+
+ assert response_ordered_by_circulating_market_cap_asc =
+ json_response(request_ordered_by_circulating_market_cap_asc, 200)
+
+ request_ordered_by_circulating_market_cap_asc_2nd_page =
+ get(
+ conn,
+ "/api/v2/tokens",
+ additional_params
+ |> Map.merge(%{"sort" => "circulating_market_cap", "order" => "asc"})
+ |> Map.merge(response_ordered_by_circulating_market_cap_asc["next_page_params"])
+ )
+
+ assert response_ordered_by_circulating_market_cap_asc_2nd_page =
+ json_response(request_ordered_by_circulating_market_cap_asc_2nd_page, 200)
+
+ check_paginated_response(
+ response_ordered_by_circulating_market_cap_asc,
+ response_ordered_by_circulating_market_cap_asc_2nd_page,
+ tokens_ordered_by_circulating_market_cap_asc
+ )
+ end
+
test "get empty list", %{conn: conn} do
request = get(conn, "/api/v2/tokens")
assert %{"items" => [], "next_page_params" => nil} = json_response(request, 200)
end
- test "check pagination", %{conn: conn} do
+ test "ignores wrong ordering params", %{conn: conn} do
tokens =
for i <- 0..50 do
- insert(:token, holder_count: i)
+ insert(:token, fiat_value: i)
end
- request = get(conn, "/api/v2/tokens")
+ request = get(conn, "/api/v2/tokens", %{"sort" => "foo", "order" => "bar"})
+
assert response = json_response(request, 200)
- request_2nd_page = get(conn, "/api/v2/tokens", response["next_page_params"])
+ request_2nd_page =
+ get(conn, "/api/v2/tokens", %{"sort" => "foo", "order" => "bar"} |> Map.merge(response["next_page_params"]))
assert response_2nd_page = json_response(request_2nd_page, 200)
-
check_paginated_response(response, response_2nd_page, tokens)
end
+ test "tokens are filtered by single type", %{conn: conn} do
+ erc_20_tokens =
+ for i <- 0..50 do
+ insert(:token, fiat_value: i)
+ end
+
+ erc_721_tokens =
+ for _i <- 0..50 do
+ insert(:token, type: "ERC-721")
+ end
+
+ erc_1155_tokens =
+ for _i <- 0..50 do
+ insert(:token, type: "ERC-1155")
+ end
+
+ check_tokens_pagination(erc_20_tokens, conn, %{"type" => "ERC-20"})
+ check_tokens_pagination(erc_721_tokens |> Enum.reverse(), conn, %{"type" => "ERC-721"})
+ check_tokens_pagination(erc_1155_tokens |> Enum.reverse(), conn, %{"type" => "ERC-1155"})
+ end
+
+ test "tokens are filtered by multiple type", %{conn: conn} do
+ erc_20_tokens =
+ for i <- 11..36 do
+ insert(:token, fiat_value: i)
+ end
+
+ erc_721_tokens =
+ for _i <- 0..25 do
+ insert(:token, type: "ERC-721")
+ end
+
+ erc_1155_tokens =
+ for _i <- 0..24 do
+ insert(:token, type: "ERC-1155")
+ end
+
+ check_tokens_pagination(
+ erc_721_tokens |> Kernel.++(erc_1155_tokens) |> Enum.reverse(),
+ conn,
+ %{
+ "type" => "ERC-1155,ERC-721"
+ }
+ )
+
+ check_tokens_pagination(
+ erc_1155_tokens |> Enum.reverse() |> Kernel.++(erc_20_tokens),
+ conn,
+ %{
+ "type" => "[erc-20,ERC-1155]"
+ }
+ )
+ end
+
+ test "sorting by fiat_value", %{conn: conn} do
+ tokens =
+ for i <- 0..50 do
+ insert(:token, fiat_value: i)
+ end
+
+ check_tokens_pagination(tokens, conn)
+ end
+
+ # these tests that tokens paginates by each parameter separately and by any combination of them
+ test "pagination by address", %{conn: conn} do
+ tokens =
+ for _i <- 0..50 do
+ insert(:token, name: nil)
+ end
+ |> Enum.reverse()
+
+ check_tokens_pagination(tokens, conn)
+ end
+
+ test "pagination by name", %{conn: conn} do
+ named_token = insert(:token, holder_count: 0)
+ empty_named_token = insert(:token, name: "", holder_count: 0)
+
+ tokens =
+ for i <- 1..49 do
+ insert(:token, holder_count: i)
+ end
+
+ tokens = [named_token, empty_named_token | tokens]
+
+ check_tokens_pagination(tokens, conn)
+ end
+
+ test "pagination by holders", %{conn: conn} do
+ tokens =
+ for i <- 0..50 do
+ insert(:token, holder_count: i, name: nil)
+ end
+
+ check_tokens_pagination(tokens, conn)
+ end
+
+ test "pagination by circulating_market_cap", %{conn: conn} do
+ tokens =
+ for i <- 0..50 do
+ insert(:token, circulating_market_cap: i, name: nil)
+ end
+
+ check_tokens_pagination(tokens, conn)
+ end
+
+ test "pagination by name and address", %{conn: conn} do
+ tokens =
+ for _i <- 0..50 do
+ insert(:token)
+ end
+ |> Enum.reverse()
+
+ check_tokens_pagination(tokens, conn)
+ end
+
+ test "pagination by holders and address", %{conn: conn} do
+ tokens =
+ for _i <- 0..50 do
+ insert(:token, holder_count: 1, name: nil)
+ end
+ |> Enum.reverse()
+
+ check_tokens_pagination(tokens, conn)
+ end
+
+ test "pagination by circulating_market_cap and address", %{conn: conn} do
+ tokens =
+ for _i <- 0..50 do
+ insert(:token, circulating_market_cap: 1, name: nil)
+ end
+ |> Enum.reverse()
+
+ check_tokens_pagination(tokens, conn)
+ end
+
+ test "pagination by holders and name", %{conn: conn} do
+ tokens =
+ for i <- 1..51 do
+ insert(:token, holder_count: 1, name: List.to_string([i]))
+ end
+ |> Enum.reverse()
+
+ check_tokens_pagination(tokens, conn)
+ end
+
+ test "pagination by circulating_market_cap and name", %{conn: conn} do
+ tokens =
+ for i <- 1..51 do
+ insert(:token, circulating_market_cap: 1, name: List.to_string([i]))
+ end
+ |> Enum.reverse()
+
+ check_tokens_pagination(tokens, conn)
+ end
+
+ test "pagination by circulating_market_cap and holders", %{conn: conn} do
+ tokens =
+ for i <- 0..50 do
+ insert(:token, circulating_market_cap: 1, holder_count: i, name: nil)
+ end
+
+ check_tokens_pagination(tokens, conn)
+ end
+
+ test "pagination by holders, name and address", %{conn: conn} do
+ tokens =
+ for _i <- 0..50 do
+ insert(:token, holder_count: 1)
+ end
+ |> Enum.reverse()
+
+ check_tokens_pagination(tokens, conn)
+ end
+
+ test "pagination by circulating_market_cap, name and address", %{conn: conn} do
+ tokens =
+ for _i <- 0..50 do
+ insert(:token, circulating_market_cap: 1)
+ end
+ |> Enum.reverse()
+
+ check_tokens_pagination(tokens, conn)
+ end
+
+ test "pagination by circulating_market_cap, holders and address", %{conn: conn} do
+ tokens =
+ for _i <- 0..50 do
+ insert(:token, circulating_market_cap: 1, holder_count: 1, name: nil)
+ end
+ |> Enum.reverse()
+
+ check_tokens_pagination(tokens, conn)
+ end
+
+ test "pagination by circulating_market_cap, holders and name", %{conn: conn} do
+ tokens =
+ for i <- 1..51 do
+ insert(:token, circulating_market_cap: 1, holder_count: 1, name: List.to_string([i]))
+ end
+ |> Enum.reverse()
+
+ check_tokens_pagination(tokens, conn)
+ end
+
+ test "pagination by circulating_market_cap, holders, name and address", %{conn: conn} do
+ tokens =
+ for _i <- 0..50 do
+ insert(:token, holder_count: 1, circulating_market_cap: 1)
+ end
+ |> Enum.reverse()
+
+ check_tokens_pagination(tokens, conn)
+ end
+
test "check nil", %{conn: conn} do
token = insert(:token)
@@ -471,6 +881,87 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do
check_paginated_response(response, response_2nd_page, instances)
end
+
+ test "get instances list by holder erc-721", %{conn: conn} do
+ token = insert(:token, type: "ERC-721")
+
+ insert_list(51, :token_instance, token_contract_address_hash: token.contract_address_hash)
+
+ address = insert(:address, contract_code: Enum.random([nil, "0x010101"]))
+
+ insert_list(51, :token_instance)
+
+ token_instances =
+ for _ <- 0..50 do
+ insert(:token_instance,
+ owner_address_hash: address.hash,
+ token_contract_address_hash: token.contract_address_hash
+ )
+ |> Repo.preload([:token, :owner])
+ end
+
+ filter = %{"holder_address_hash" => to_string(address.hash)}
+
+ request = get(conn, "/api/v2/tokens/#{token.contract_address_hash}/instances", filter)
+ assert response = json_response(request, 200)
+
+ request_2nd_page =
+ get(
+ conn,
+ "/api/v2/tokens/#{token.contract_address_hash}/instances",
+ Map.merge(response["next_page_params"], filter)
+ )
+
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ check_paginated_response(response, response_2nd_page, token_instances)
+ end
+
+ test "get instances list by holder erc-1155", %{conn: conn} do
+ token = insert(:token, type: "ERC-1155")
+
+ insert_list(51, :token_instance, token_contract_address_hash: token.contract_address_hash)
+
+ address = insert(:address, contract_code: Enum.random([nil, "0x010101"]))
+
+ insert_list(51, :token_instance)
+
+ token_instances =
+ for _ <- 0..50 do
+ ti =
+ insert(:token_instance,
+ token_contract_address_hash: token.contract_address_hash
+ )
+ |> Repo.preload([:token])
+
+ current_token_balance =
+ insert(:address_current_token_balance_with_token_id_and_fixed_token_type,
+ address: address,
+ token_type: "ERC-1155",
+ token_id: ti.token_id,
+ token_contract_address_hash: token.contract_address_hash,
+ value: Enum.random(1..2)
+ )
+
+ %Instance{ti | current_token_balance: current_token_balance, owner: address}
+ end
+
+ filter = %{"holder_address_hash" => to_string(address.hash)}
+
+ request = get(conn, "/api/v2/tokens/#{token.contract_address_hash}/instances", filter)
+ assert response = json_response(request, 200)
+
+ request_2nd_page =
+ get(
+ conn,
+ "/api/v2/tokens/#{token.contract_address_hash}/instances",
+ Map.merge(response["next_page_params"], filter)
+ )
+
+ assert response_2nd_page = json_response(request_2nd_page, 200)
+
+ check_paginated_response(response, response_2nd_page, token_instances)
+ end
end
describe "/tokens/{address_hash}/instances/{token_id}" do
@@ -517,7 +1008,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do
assert data = json_response(request, 200)
assert compare_item(instance, data)
- assert compare_item(transfer.to_address, data["owner"])
+ assert Address.checksum(instance.owner_address_hash) == data["owner"]["hash"]
end
end
@@ -846,6 +1337,40 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do
compare_item(Repo.preload(ctb, [{:token, :contract_address}]).token, json["token"])
end
+ def compare_item(%Instance{token: %Token{} = token} = instance, json) do
+ token_type = token.type
+ value = to_string(value(token.type, instance))
+ id = to_string(instance.token_id)
+ metadata = instance.metadata
+ token_address_hash = Address.checksum(token.contract_address_hash)
+ app_url = instance.metadata["external_url"]
+ animation_url = instance.metadata["animation_url"]
+ image_url = instance.metadata["image_url"]
+ token_name = token.name
+ owner_address_hash = Address.checksum(instance.owner.hash)
+ is_contract = !is_nil(instance.owner.contract_code)
+ is_unique = value == "1"
+
+ assert %{
+ "token_type" => ^token_type,
+ "value" => ^value,
+ "id" => ^id,
+ "metadata" => ^metadata,
+ "token" => %{"address" => ^token_address_hash, "name" => ^token_name, "type" => ^token_type},
+ "external_app_url" => ^app_url,
+ "animation_url" => ^animation_url,
+ "image_url" => ^image_url,
+ "is_unique" => ^is_unique
+ } = json
+
+ if is_unique do
+ assert owner_address_hash == json["owner"]["hash"]
+ assert is_contract == json["owner"]["is_contract"]
+ else
+ assert json["owner"] == nil
+ end
+ end
+
def compare_item(%Instance{} = instance, json) do
assert to_string(instance.token_id) == json["id"]
assert Jason.decode!(Jason.encode!(instance.metadata)) == json["metadata"]
@@ -853,6 +1378,14 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do
compare_item(Repo.preload(instance, [{:token, :contract_address}]).token, json["token"])
end
+ defp value("ERC-721", _), do: 1
+ defp value(_, nft), do: nft.current_token_balance.value
+
+ # with the current implementation no transfers should come with list in totals
+ def check_total(%Token{type: nft}, json, _token_transfer) when nft in ["ERC-721", "ERC-1155"] and is_list(json) do
+ false
+ end
+
def check_total(%Token{type: nft}, json, token_transfer) when nft in ["ERC-1155"] do
json["token_id"] in Enum.map(token_transfer.token_ids, fn x -> to_string(x) end) and
json["value"] == to_string(token_transfer.amount)
@@ -862,11 +1395,6 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do
json["token_id"] in Enum.map(token_transfer.token_ids, fn x -> to_string(x) end)
end
- # with the current implementation no transfers should come with list in totals
- def check_total(%Token{type: nft}, json, _token_transfer) when nft in ["ERC-721", "ERC-1155"] and is_list(json) do
- false
- end
-
def check_total(_, _, _), do: true
defp check_paginated_response(first_page_resp, second_page_resp, list) do
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs
index 1e3073d798b8..9fdad1782da8 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs
@@ -1,15 +1,20 @@
defmodule BlockScoutWeb.API.V2.TransactionControllerTest do
use BlockScoutWeb.ConnCase
- import EthereumJSONRPC, only: [integer_to_quantity: 1]
import Explorer.Chain, only: [hash_to_lower_case_string: 1]
- import Mox
alias BlockScoutWeb.Models.UserFromAuth
alias Explorer.Account.WatchlistAddress
alias Explorer.Chain.{Address, InternalTransaction, Log, Token, TokenTransfer, Transaction}
alias Explorer.Repo
+ @first_topic_hex_string_1 "0x99e7b0ba56da2819c37c047f0511fd2bf6c9b4e27b4a979a19d6da0f74be8155"
+
+ defp topic(topic_hex_string) do
+ {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string)
+ topic
+ end
+
setup do
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.TransactionsApiV2.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.TransactionsApiV2.child_id())
@@ -109,7 +114,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do
|> with_block()
auth = build(:auth)
- address = insert(:address)
+ insert(:address)
{:ok, user} = UserFromAuth.find_or_create(auth)
conn = Plug.Test.init_test_session(conn, current_user: user)
@@ -229,6 +234,31 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do
test "batch 1155 flattened", %{conn: conn} do
token = insert(:token, type: "ERC-1155")
+ tx =
+ :transaction
+ |> insert()
+ |> with_block()
+
+ insert(:token_transfer,
+ transaction: tx,
+ block: tx.block,
+ block_number: tx.block_number,
+ token_contract_address: token.contract_address,
+ token_ids: Enum.map(0..50, fn x -> x end),
+ amounts: Enum.map(0..50, fn x -> x end)
+ )
+
+ request = get(conn, "/api/v2/transactions/" <> to_string(tx.hash))
+
+ assert response = json_response(request, 200)
+ compare_item(tx, response)
+
+ assert Enum.count(response["token_transfers"]) == 10
+ end
+
+ test "single 1155 flattened", %{conn: conn} do
+ token = insert(:token, type: "ERC-1155")
+
tx =
:transaction
|> insert()
@@ -240,8 +270,9 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do
block: tx.block,
block_number: tx.block_number,
token_contract_address: token.contract_address,
- token_ids: Enum.map(0..50, fn x -> x end),
- amounts: Enum.map(0..50, fn x -> x end)
+ token_ids: [1],
+ amounts: [2],
+ amount: nil
)
request = get(conn, "/api/v2/transactions/" <> to_string(tx.hash))
@@ -249,7 +280,9 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do
assert response = json_response(request, 200)
compare_item(tx, response)
- assert Enum.count(response["token_transfers"]) == 10
+ assert Enum.count(response["token_transfers"]) == 1
+ assert is_map(Enum.at(response["token_transfers"], 0)["total"])
+ assert compare_item(%TokenTransfer{tt | amount: 2}, Enum.at(response["token_transfers"], 0))
end
end
@@ -896,64 +929,174 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do
end
test "return existing tx", %{conn: conn} do
- EthereumJSONRPC.Mox
- |> stub(:json_rpc, fn
- [%{id: id, method: "eth_getBalance", params: _}], _options ->
- {:ok, [%{id: id, result: integer_to_quantity(123)}]}
-
- [%{id: _id, method: "eth_getBlockByNumber", params: _}], _options ->
- {:ok,
- [
- %{
- id: 0,
- jsonrpc: "2.0",
- result: %{
- "author" => "0x0000000000000000000000000000000000000000",
- "difficulty" => "0x20000",
- "extraData" => "0x",
- "gasLimit" => "0x663be0",
- "gasUsed" => "0x0",
- "hash" => "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f",
- "logsBloom" =>
- "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
- "miner" => "0x0000000000000000000000000000000000000000",
- "number" => integer_to_quantity(1),
- "parentHash" => "0x0000000000000000000000000000000000000000000000000000000000000000",
- "receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
- "sealFields" => [
- "0x80",
- "0xb8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
- ],
- "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
- "signature" =>
- "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
- "size" => "0x215",
- "stateRoot" => "0xfad4af258fd11939fae0c6c6eec9d340b1caac0b0196fd9a1bc3f489c5bf00b3",
- "step" => "0",
- "timestamp" => "0x0",
- "totalDifficulty" => "0x20000",
- "transactions" => [],
- "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
- "uncles" => []
- }
- }
- ]}
- end)
-
- insert(:block)
- insert(:block)
- address_a = insert(:address)
- address_b = insert(:address)
+ block_before = insert(:block)
transaction =
:transaction
- |> insert(from_address: address_a, to_address: address_b, value: 1000)
+ |> insert()
|> with_block(status: :ok)
+ insert(:address_coin_balance,
+ address: transaction.from_address,
+ address_hash: transaction.from_address_hash,
+ block_number: block_before.number
+ )
+
+ insert(:address_coin_balance,
+ address: transaction.to_address,
+ address_hash: transaction.to_address_hash,
+ block_number: block_before.number
+ )
+
+ insert(:address_coin_balance,
+ address: transaction.block.miner,
+ address_hash: transaction.block.miner_hash,
+ block_number: block_before.number
+ )
+
request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/state-changes")
assert response = json_response(request, 200)
- assert Enum.count(response) == 3
+ assert Enum.count(response["items"]) == 3
+ end
+ end
+
+ describe "stability fees" do
+ setup %{conn: conn} do
+ old_env = Application.get_env(:explorer, :chain_type)
+
+ Application.put_env(:explorer, :chain_type, "stability")
+
+ on_exit(fn ->
+ Application.put_env(:explorer, :chain_type, old_env)
+ end)
+
+ %{conn: conn}
+ end
+
+ test "check stability fees", %{conn: conn} do
+ tx = insert(:transaction) |> with_block()
+
+ _log =
+ insert(:log,
+ transaction: tx,
+ index: 1,
+ block: tx.block,
+ block_number: tx.block_number,
+ first_topic: topic(@first_topic_hex_string_1),
+ data:
+ "0x000000000000000000000000dc2b93f3291030f3f7a6d9363ac37757f7ad5c4300000000000000000000000000000000000000000000000000002824369a100000000000000000000000000046b555cb3962bf9533c437cbd04a2f702dfdb999000000000000000000000000000000000000000000000000000014121b4d0800000000000000000000000000faf7a981360c2fab3a5ab7b3d6d8d0cf97a91eb9000000000000000000000000000000000000000000000000000014121b4d0800"
+ )
+
+ insert(:token, contract_address: build(:address, hash: "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43"))
+ request = get(conn, "/api/v2/transactions")
+
+ assert %{
+ "items" => [
+ %{
+ "stability_fee" => %{
+ "token" => %{"address" => "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43"},
+ "validator_address" => %{"hash" => "0x46B555CB3962bF9533c437cBD04A2f702dfdB999"},
+ "dapp_address" => %{"hash" => "0xFAf7a981360c2FAb3a5Ab7b3D6d8D0Cf97a91Eb9"},
+ "total_fee" => "44136000000000",
+ "dapp_fee" => "22068000000000",
+ "validator_fee" => "22068000000000"
+ }
+ }
+ ]
+ } = json_response(request, 200)
+
+ request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}")
+
+ assert %{
+ "stability_fee" => %{
+ "token" => %{"address" => "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43"},
+ "validator_address" => %{"hash" => "0x46B555CB3962bF9533c437cBD04A2f702dfdB999"},
+ "dapp_address" => %{"hash" => "0xFAf7a981360c2FAb3a5Ab7b3D6d8D0Cf97a91Eb9"},
+ "total_fee" => "44136000000000",
+ "dapp_fee" => "22068000000000",
+ "validator_fee" => "22068000000000"
+ }
+ } = json_response(request, 200)
+
+ request = get(conn, "/api/v2/addresses/#{to_string(tx.from_address_hash)}/transactions")
+
+ assert %{
+ "items" => [
+ %{
+ "stability_fee" => %{
+ "token" => %{"address" => "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43"},
+ "validator_address" => %{"hash" => "0x46B555CB3962bF9533c437cBD04A2f702dfdB999"},
+ "dapp_address" => %{"hash" => "0xFAf7a981360c2FAb3a5Ab7b3D6d8D0Cf97a91Eb9"},
+ "total_fee" => "44136000000000",
+ "dapp_fee" => "22068000000000",
+ "validator_fee" => "22068000000000"
+ }
+ }
+ ]
+ } = json_response(request, 200)
+ end
+
+ test "check stability if token absent in DB", %{conn: conn} do
+ tx = insert(:transaction) |> with_block()
+
+ _log =
+ insert(:log,
+ transaction: tx,
+ index: 1,
+ block: tx.block,
+ block_number: tx.block_number,
+ first_topic: topic(@first_topic_hex_string_1),
+ data:
+ "0x000000000000000000000000dc2b93f3291030f3f7a6d9363ac37757f7ad5c4300000000000000000000000000000000000000000000000000002824369a100000000000000000000000000046b555cb3962bf9533c437cbd04a2f702dfdb999000000000000000000000000000000000000000000000000000014121b4d0800000000000000000000000000faf7a981360c2fab3a5ab7b3d6d8d0cf97a91eb9000000000000000000000000000000000000000000000000000014121b4d0800"
+ )
+
+ request = get(conn, "/api/v2/transactions")
+
+ assert %{
+ "items" => [
+ %{
+ "stability_fee" => %{
+ "token" => %{"address" => "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43"},
+ "validator_address" => %{"hash" => "0x46B555CB3962bF9533c437cBD04A2f702dfdB999"},
+ "dapp_address" => %{"hash" => "0xFAf7a981360c2FAb3a5Ab7b3D6d8D0Cf97a91Eb9"},
+ "total_fee" => "44136000000000",
+ "dapp_fee" => "22068000000000",
+ "validator_fee" => "22068000000000"
+ }
+ }
+ ]
+ } = json_response(request, 200)
+
+ request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}")
+
+ assert %{
+ "stability_fee" => %{
+ "token" => %{"address" => "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43"},
+ "validator_address" => %{"hash" => "0x46B555CB3962bF9533c437cBD04A2f702dfdB999"},
+ "dapp_address" => %{"hash" => "0xFAf7a981360c2FAb3a5Ab7b3D6d8D0Cf97a91Eb9"},
+ "total_fee" => "44136000000000",
+ "dapp_fee" => "22068000000000",
+ "validator_fee" => "22068000000000"
+ }
+ } = json_response(request, 200)
+
+ request = get(conn, "/api/v2/addresses/#{to_string(tx.from_address_hash)}/transactions")
+
+ assert %{
+ "items" => [
+ %{
+ "stability_fee" => %{
+ "token" => %{"address" => "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43"},
+ "validator_address" => %{"hash" => "0x46B555CB3962bF9533c437cBD04A2f702dfdB999"},
+ "dapp_address" => %{"hash" => "0xFAf7a981360c2FAb3a5Ab7b3D6d8D0Cf97a91Eb9"},
+ "total_fee" => "44136000000000",
+ "dapp_fee" => "22068000000000",
+ "validator_fee" => "22068000000000"
+ }
+ }
+ ]
+ } = json_response(request, 200)
end
end
@@ -965,6 +1108,34 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do
assert Address.checksum(transaction.to_address_hash) == json["to"]["hash"]
end
+ defp compare_item(%InternalTransaction{} = internal_tx, json) do
+ assert internal_tx.block_number == json["block"]
+ assert to_string(internal_tx.gas) == json["gas_limit"]
+ assert internal_tx.index == json["index"]
+ assert to_string(internal_tx.transaction_hash) == json["transaction_hash"]
+ assert Address.checksum(internal_tx.from_address_hash) == json["from"]["hash"]
+ assert Address.checksum(internal_tx.to_address_hash) == json["to"]["hash"]
+ end
+
+ defp compare_item(%Log{} = log, json) do
+ assert to_string(log.data) == json["data"]
+ assert log.index == json["index"]
+ assert Address.checksum(log.address_hash) == json["address"]["hash"]
+ assert to_string(log.transaction_hash) == json["tx_hash"]
+ assert json["block_number"] == log.block_number
+ end
+
+ defp compare_item(%TokenTransfer{} = token_transfer, json) do
+ assert Address.checksum(token_transfer.from_address_hash) == json["from"]["hash"]
+ assert Address.checksum(token_transfer.to_address_hash) == json["to"]["hash"]
+ assert to_string(token_transfer.transaction_hash) == json["tx_hash"]
+ assert json["timestamp"] == nil
+ assert json["method"] == nil
+ assert to_string(token_transfer.block_hash) == json["block_hash"]
+ assert to_string(token_transfer.log_index) == json["log_index"]
+ assert check_total(Repo.preload(token_transfer, [{:token, :contract_address}]).token, json["total"], token_transfer)
+ end
+
defp compare_item(%Transaction{} = transaction, json, wl_names) do
assert to_string(transaction.hash) == json["hash"]
assert transaction.block_number == json["block"]
@@ -995,33 +1166,6 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do
)
end
- defp compare_item(%InternalTransaction{} = internal_tx, json) do
- assert internal_tx.block_number == json["block"]
- assert to_string(internal_tx.gas) == json["gas_limit"]
- assert internal_tx.index == json["index"]
- assert to_string(internal_tx.transaction_hash) == json["transaction_hash"]
- assert Address.checksum(internal_tx.from_address_hash) == json["from"]["hash"]
- assert Address.checksum(internal_tx.to_address_hash) == json["to"]["hash"]
- end
-
- defp compare_item(%Log{} = log, json) do
- assert to_string(log.data) == json["data"]
- assert log.index == json["index"]
- assert Address.checksum(log.address_hash) == json["address"]["hash"]
- assert to_string(log.transaction_hash) == json["tx_hash"]
- end
-
- defp compare_item(%TokenTransfer{} = token_transfer, json) do
- assert Address.checksum(token_transfer.from_address_hash) == json["from"]["hash"]
- assert Address.checksum(token_transfer.to_address_hash) == json["to"]["hash"]
- assert to_string(token_transfer.transaction_hash) == json["tx_hash"]
- assert json["timestamp"] == nil
- assert json["method"] == nil
- assert to_string(token_transfer.block_hash) == json["block_hash"]
- assert to_string(token_transfer.log_index) == json["log_index"]
- assert check_total(Repo.preload(token_transfer, [{:token, :contract_address}]).token, json["total"], token_transfer)
- end
-
defp check_paginated_response(first_page_resp, second_page_resp, txs) do
assert Enum.count(first_page_resp["items"]) == 50
assert first_page_resp["next_page_params"] != nil
@@ -1044,6 +1188,11 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do
compare_item(Enum.at(txs, 0), Enum.at(second_page_resp["items"], 0), wl_names)
end
+ # with the current implementation no transfers should come with list in totals
+ defp check_total(%Token{type: nft}, json, _token_transfer) when nft in ["ERC-721", "ERC-1155"] and is_list(json) do
+ false
+ end
+
defp check_total(%Token{type: nft}, json, token_transfer) when nft in ["ERC-1155"] do
json["token_id"] in Enum.map(token_transfer.token_ids, fn x -> to_string(x) end) and
json["value"] == to_string(token_transfer.amount)
@@ -1053,10 +1202,5 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do
json["token_id"] in Enum.map(token_transfer.token_ids, fn x -> to_string(x) end)
end
- # with the current implementation no transfers should come with list in totals
- defp check_total(%Token{type: nft}, json, _token_transfer) when nft in ["ERC-721", "ERC-1155"] and is_list(json) do
- false
- end
-
defp check_total(_, _, _), do: true
end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/verification_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/verification_controller_test.exs
index 4ff1545377bd..bd471e8d771f 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/verification_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/verification_controller_test.exs
@@ -75,7 +75,7 @@ defmodule BlockScoutWeb.API.V2.VerificationControllerTest do
request = post(conn, "/api/v2/smart-contracts/#{contract_address.hash}/verification/via/flattened-code", params)
- assert %{"message" => "Verification started"} = json_response(request, 200)
+ assert %{"message" => "Smart-contract verification started"} = json_response(request, 200)
assert_receive %Phoenix.Socket.Message{
payload: %{status: "success"},
@@ -118,7 +118,7 @@ defmodule BlockScoutWeb.API.V2.VerificationControllerTest do
request = post(conn, "/api/v2/smart-contracts/#{contract_address.hash}/verification/via/flattened-code", params)
- assert %{"message" => "Verification started"} = json_response(request, 200)
+ assert %{"message" => "Smart-contract verification started"} = json_response(request, 200)
assert_receive %Phoenix.Socket.Message{
payload: %{status: "error", errors: %{name: ["Wrong contract name, please try again."]}},
@@ -198,7 +198,7 @@ defmodule BlockScoutWeb.API.V2.VerificationControllerTest do
body
)
- assert %{"message" => "Verification started"} = json_response(request, 200)
+ assert %{"message" => "Smart-contract verification started"} = json_response(request, 200)
assert_receive %Phoenix.Socket.Message{
payload: %{status: "success"},
@@ -222,7 +222,7 @@ defmodule BlockScoutWeb.API.V2.VerificationControllerTest do
end
test "verify contract from sourcify repo", %{conn: conn} do
- address = "0x18d89C12e9463Be6343c35C9990361bA4C42AfC2"
+ address = "0xf26594F585De4EB0Ae9De865d9053FEe02ac6eF1"
_contract = insert(:address, hash: address, contract_code: "0x01")
@@ -259,7 +259,7 @@ defmodule BlockScoutWeb.API.V2.VerificationControllerTest do
body
)
- assert %{"message" => "Verification started"} = json_response(request, 200)
+ assert %{"message" => "Smart-contract verification started"} = json_response(request, 200)
assert_receive %Phoenix.Socket.Message{
payload: %{status: "success"},
@@ -329,7 +329,7 @@ defmodule BlockScoutWeb.API.V2.VerificationControllerTest do
request = post(conn, "/api/v2/smart-contracts/#{contract_address.hash}/verification/via/vyper-code", params)
- assert %{"message" => "Verification started"} = json_response(request, 200)
+ assert %{"message" => "Smart-contract verification started"} = json_response(request, 200)
assert_receive %Phoenix.Socket.Message{
payload: %{status: "success"},
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/withdrawal_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/withdrawal_controller_test.exs
index 4645958e9263..88320ec50b5b 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/withdrawal_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/withdrawal_controller_test.exs
@@ -5,7 +5,7 @@ defmodule BlockScoutWeb.API.V2.WithdrawalControllerTest do
describe "/withdrawals" do
test "empty lists", %{conn: conn} do
- request = get(conn, "/api/v2/blocks")
+ request = get(conn, "/api/v2/withdrawals")
assert response = json_response(request, 200)
assert response["items"] == []
assert response["next_page_params"] == nil
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs
index b407e0a44c32..8c5333adfc38 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs
@@ -134,7 +134,7 @@ defmodule BlockScoutWeb.BlockControllerTest do
test "returns all reorgs", %{conn: conn} do
4
|> insert_list(:block, consensus: false)
- |> Enum.map(& &1.hash)
+ |> Enum.each(fn b -> insert(:block, number: b.number, consensus: true) end)
conn = get(conn, reorg_path(conn, :reorg), %{"type" => "JSON"})
@@ -146,7 +146,7 @@ defmodule BlockScoutWeb.BlockControllerTest do
test "does not include blocks or uncles", %{conn: conn} do
4
|> insert_list(:block, consensus: false)
- |> Enum.map(& &1.hash)
+ |> Enum.each(fn b -> insert(:block, number: b.number, consensus: true) end)
insert(:block)
uncle = insert(:block, consensus: false)
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs
index 5a52e764a3cd..8d2889876964 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs
@@ -86,6 +86,8 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
contract_code_md5: "123"
)
+ get_eip1967_implementation_zero_addresses()
+
path =
smart_contract_path(BlockScoutWeb.Endpoint, :index,
hash: token_contract_address.hash,
@@ -276,62 +278,99 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
end
defp blockchain_get_implementation_mock do
- expect(
- EthereumJSONRPC.Mox,
- :json_rpc,
- fn %{id: _, method: _, params: [_, _, _]}, _options ->
- {:ok, "0xcebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"}
- end
- )
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0xcebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"}
+ end)
end
defp blockchain_get_implementation_mock_2 do
- expect(
- EthereumJSONRPC.Mox,
- :json_rpc,
- fn %{id: _, method: _, params: [_, _, _]}, _options ->
- {:ok, "0x000000000000000000000000cebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"}
- end
- )
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x000000000000000000000000cebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"}
+ end)
end
- def get_eip1967_implementation do
- EthereumJSONRPC.Mox
- |> expect(:json_rpc, fn %{
- id: 0,
- method: "eth_getStorageAt",
- params: [
- _,
- "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
- "latest"
- ]
- },
- _options ->
+ defp mock_empty_logic_storage_pointer_request do
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
+ "latest"
+ ]
+ },
+ _options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
- |> expect(:json_rpc, fn %{
- id: 0,
- method: "eth_getStorageAt",
- params: [
- _,
- "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50",
- "latest"
- ]
- },
- _options ->
+ end
+
+ defp mock_empty_beacon_storage_pointer_request(mox) do
+ expect(mox, :json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50",
+ "latest"
+ ]
+ },
+ _options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
- |> expect(:json_rpc, fn %{
- id: 0,
- method: "eth_getStorageAt",
- params: [
- _,
- "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3",
- "latest"
- ]
- },
- _options ->
+ end
+
+ defp mock_empty_eip_1822_storage_pointer_request(mox) do
+ expect(mox, :json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
+ "latest"
+ ]
+ },
+ _options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
end
+
+ defp mock_empty_oz_storage_pointer_request(mox) do
+ expect(mox, :json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
+ end
+
+ def get_eip1967_implementation_zero_addresses do
+ mock_empty_logic_storage_pointer_request()
+ |> mock_empty_beacon_storage_pointer_request()
+ |> mock_empty_oz_storage_pointer_request()
+ |> mock_empty_eip_1822_storage_pointer_request()
+ end
end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs
index 799b54da797c..e15f85569214 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs
@@ -126,7 +126,7 @@ defmodule BlockScoutWeb.Tokens.InventoryControllerTest do
transaction: transaction,
token_contract_address: token.contract_address,
token: token,
- token_id: 1000
+ token_ids: [1000]
)
conn = get(conn, token_inventory_path(conn, :index, token.contract_address_hash), %{type: "JSON"})
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/tokens/read_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/tokens/read_contract_controller_test.exs
index ab5ba16f9c5d..7a03cfba35f7 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/tokens/read_contract_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/tokens/read_contract_controller_test.exs
@@ -3,6 +3,8 @@ defmodule BlockScoutWeb.Tokens.ContractControllerTest do
import Mox
+ setup :verify_on_exit!
+
describe "GET index/3" do
test "with invalid address hash", %{conn: conn} do
conn = get(conn, token_read_contract_path(BlockScoutWeb.Endpoint, :index, "invalid_address"))
@@ -53,7 +55,7 @@ defmodule BlockScoutWeb.Tokens.ContractControllerTest do
token: token
)
- get_eip1967_implementation()
+ request_zero_implementations()
conn = get(conn, token_read_contract_path(BlockScoutWeb.Endpoint, :index, token.contract_address_hash))
@@ -62,7 +64,7 @@ defmodule BlockScoutWeb.Tokens.ContractControllerTest do
end
end
- def get_eip1967_implementation do
+ def request_zero_implementations do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
@@ -100,5 +102,17 @@ defmodule BlockScoutWeb.Tokens.ContractControllerTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
end
end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs
index 4969db368530..71deea5dbf43 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs
@@ -1,8 +1,6 @@
defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do
use BlockScoutWeb.ConnCase
- import Mox
-
import BlockScoutWeb.WebRouter.Helpers, only: [transaction_internal_transaction_path: 3]
alias Explorer.Chain.InternalTransaction
@@ -75,11 +73,6 @@ defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do
end
test "includes USD exchange rate value for address in assigns", %{conn: conn} do
- EthereumJSONRPC.Mox
- |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options ->
- {:ok, "100"}
- end)
-
transaction = insert(:transaction)
conn = get(conn, transaction_internal_transaction_path(BlockScoutWeb.Endpoint, :index, transaction.hash))
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs
index fc9ced3bba5f..57f8933c243c 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs
@@ -1,8 +1,6 @@
defmodule BlockScoutWeb.TransactionLogControllerTest do
use BlockScoutWeb.ConnCase
- import Mox
-
import BlockScoutWeb.WebRouter.Helpers, only: [transaction_log_path: 3]
alias Explorer.Chain.Address
@@ -157,11 +155,6 @@ defmodule BlockScoutWeb.TransactionLogControllerTest do
end
test "includes USD exchange rate value for address in assigns", %{conn: conn} do
- EthereumJSONRPC.Mox
- |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options ->
- {:ok, "100"}
- end)
-
transaction = insert(:transaction)
conn = get(conn, transaction_log_path(BlockScoutWeb.Endpoint, :index, transaction.hash))
@@ -170,11 +163,6 @@ defmodule BlockScoutWeb.TransactionLogControllerTest do
end
test "loads for transactions that created a contract", %{conn: conn} do
- EthereumJSONRPC.Mox
- |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options ->
- {:ok, "100"}
- end)
-
contract_address = insert(:contract_address)
transaction =
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_state_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_state_controller_test.exs
index 956c7f2b9b91..ac5898f37f37 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/transaction_state_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_state_controller_test.exs
@@ -7,6 +7,31 @@ defmodule BlockScoutWeb.TransactionStateControllerTest do
import BlockScoutWeb.WeiHelper, only: [format_wei_value: 2]
import EthereumJSONRPC, only: [integer_to_quantity: 1]
alias Explorer.Chain.Wei
+ alias Indexer.Fetcher.CoinBalance
+ alias Explorer.Counters.{AddressesCounter, AverageBlockTime}
+ alias Indexer.Fetcher.CoinBalanceOnDemand
+
+ setup :set_mox_global
+
+ setup :verify_on_exit!
+
+ setup do
+ mocked_json_rpc_named_arguments = [
+ transport: EthereumJSONRPC.Mox,
+ transport_options: []
+ ]
+
+ start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
+ start_supervised!(AverageBlockTime)
+ start_supervised!(AddressesCounter)
+ start_supervised!({CoinBalanceOnDemand, [mocked_json_rpc_named_arguments, [name: CoinBalanceOnDemand]]})
+
+ Application.put_env(:explorer, AverageBlockTime, enabled: true, cache_period: 1_800_000)
+
+ on_exit(fn ->
+ Application.put_env(:explorer, AverageBlockTime, enabled: false, cache_period: 1_800_000)
+ end)
+ end
describe "GET index/3" do
test "loads existing transaction", %{conn: conn} do
@@ -156,16 +181,19 @@ defmodule BlockScoutWeb.TransactionStateControllerTest do
end
test "fetch coin balances if needed", %{conn: conn} do
+ json_rpc_named_arguments = Application.fetch_env!(:indexer, :json_rpc_named_arguments)
+ CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
+
EthereumJSONRPC.Mox
|> stub(:json_rpc, fn
[%{id: id, method: "eth_getBalance", params: _}], _options ->
{:ok, [%{id: id, result: integer_to_quantity(123)}]}
- [%{id: _id, method: "eth_getBlockByNumber", params: _}], _options ->
+ [%{id: id, method: "eth_getBlockByNumber", params: _}], _options ->
{:ok,
[
%{
- id: 0,
+ id: id,
jsonrpc: "2.0",
result: %{
"author" => "0x0000000000000000000000000000000000000000",
@@ -215,6 +243,13 @@ defmodule BlockScoutWeb.TransactionStateControllerTest do
{:ok, %{"items" => items}} = conn.resp_body |> Poison.decode()
full_text = Enum.join(items)
+ assert(length(items) == 3)
+ assert(String.contains?(full_text, format_wei_value(%Wei{value: Decimal.new(0)}, :ether)))
+
+ 1 |> :timer.seconds() |> :timer.sleep()
+ conn = get(conn, transaction_state_path(conn, :index, transaction), %{type: "JSON"})
+ {:ok, %{"items" => items}} = conn.resp_body |> Poison.decode()
+ full_text = Enum.join(items)
assert(String.contains?(full_text, format_wei_value(%Wei{value: Decimal.new(123)}, :ether)))
assert(length(items) == 3)
end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs
index 6b789a2d0f4c..398d84dd8acd 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs
@@ -7,13 +7,10 @@ defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do
alias Explorer.ExchangeRates.Token
+ setup :verify_on_exit!
+
describe "GET index/3" do
test "load token transfers", %{conn: conn} do
- EthereumJSONRPC.Mox
- |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options ->
- {:ok, "100"}
- end)
-
transaction = insert(:transaction)
token_transfer = insert(:token_transfer, transaction: transaction)
@@ -71,11 +68,6 @@ defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do
end
test "includes USD exchange rate value for address in assigns", %{conn: conn} do
- EthereumJSONRPC.Mox
- |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options ->
- {:ok, "100"}
- end)
-
transaction = insert(:transaction)
conn = get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, transaction.hash))
@@ -201,8 +193,17 @@ defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
- |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options ->
- {:ok, "100"}
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
transaction = insert(:transaction_to_verified_contract)
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/verified_contracts_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/verified_contracts_controller_test.exs
index dd5327ca7cca..55936934dcdb 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/verified_contracts_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/verified_contracts_controller_test.exs
@@ -65,7 +65,9 @@ defmodule BlockScoutWeb.VerifiedContractsControllerTest do
expected_path =
verified_contracts_path(conn, :index, %{
smart_contract_id: id,
- items_count: "50"
+ items_count: "50",
+ coin_balance: nil,
+ tx_count: nil
})
assert Map.get(json_response(conn, 200), "next_page_path") == expected_path
@@ -113,6 +115,23 @@ defmodule BlockScoutWeb.VerifiedContractsControllerTest do
assert String.contains?(smart_contracts_tile, "data-identifier-hash=\"#{to_string(vyper_hash)}\"")
end
+ test "returns yul contract", %{conn: conn} do
+ %SmartContract{address_hash: yul_hash} = insert(:smart_contract, abi: nil)
+ insert(:smart_contract)
+
+ path =
+ verified_contracts_path(conn, :index, %{
+ "filter" => "yul",
+ "type" => "JSON"
+ })
+
+ conn = get(conn, path)
+
+ [smart_contracts_tile] = json_response(conn, 200)["items"]
+
+ assert String.contains?(smart_contracts_tile, "data-identifier-hash=\"#{to_string(yul_hash)}\"")
+ end
+
test "returns search results by name", %{conn: conn} do
insert(:smart_contract)
insert(:smart_contract)
diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs
index c1f6688d4d0d..914ef68edb16 100644
--- a/apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs
@@ -174,7 +174,8 @@ defmodule BlockScoutWeb.ViewingBlocksTest do
describe "viewing reorg blocks list" do
test "lists uncle blocks", %{session: session} do
- [reorg | _] = insert_list(10, :block, consensus: false)
+ [reorg | _] = blocks = insert_list(10, :block, consensus: false)
+ Enum.each(blocks, fn b -> insert(:block, number: b.number, consensus: true) end)
session
|> BlockListPage.visit_reorgs_page()
diff --git a/apps/block_scout_web/test/block_scout_web/schema/query/address_test.exs b/apps/block_scout_web/test/block_scout_web/schema/query/address_test.exs
index ea1f5579f01c..f4526bc31454 100644
--- a/apps/block_scout_web/test/block_scout_web/schema/query/address_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/schema/query/address_test.exs
@@ -18,7 +18,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressTest do
variables = %{"hash" => to_string(address.hash)}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert json_response(conn, 200) == %{
"data" => %{
@@ -45,7 +45,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressTest do
variables = %{"hash" => to_string(address.hash)}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert json_response(conn, 200) == %{
"data" => %{
@@ -78,7 +78,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressTest do
variables = %{"hash" => to_string(address.hash)}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert json_response(conn, 200) == %{
"data" => %{
@@ -110,7 +110,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressTest do
variables = %{"hash" => to_string(address.hash)}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert %{"errors" => [error]} = json_response(conn, 200)
assert error["message"] =~ ~s(Address not found.)
@@ -127,7 +127,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressTest do
variables = %{}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert %{"errors" => [error]} = json_response(conn, 200)
assert error["message"] == ~s(In argument "hash": Expected type "AddressHash!", found null.)
@@ -144,7 +144,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressTest do
variables = %{"hash" => "someInvalidHash"}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert %{"errors" => [error]} = json_response(conn, 200)
assert error["message"] =~ ~s(Argument "hash" has invalid value)
@@ -193,7 +193,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressTest do
"first" => 1
}
- conn = post(conn, "/graphql", query: query, variables: variables)
+ conn = post(conn, "/api/v1/graphql", query: query, variables: variables)
assert json_response(conn, 200) == %{
"data" => %{
@@ -251,7 +251,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressTest do
"first" => 1
}
- conn = post(conn, "/graphql", query: query, variables: variables)
+ conn = post(conn, "/api/v1/graphql", query: query, variables: variables)
assert json_response(conn, 200) == %{
"data" => %{
@@ -304,7 +304,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressTest do
"first" => 3
}
- conn = post(conn, "/graphql", query: query, variables: variables)
+ conn = post(conn, "/api/v1/graphql", query: query, variables: variables)
%{
"data" => %{
@@ -366,7 +366,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressTest do
"first" => 3
}
- conn = post(conn, "/graphql", query: query, variables: variables)
+ conn = post(conn, "/api/v1/graphql", query: query, variables: variables)
%{
"data" => %{
@@ -410,7 +410,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressTest do
"first" => 67
}
- conn = post(conn, "/graphql", query: query, variables: variables)
+ conn = post(conn, "/api/v1/graphql", query: query, variables: variables)
assert %{"errors" => [error1, error2, error3]} = json_response(conn, 200)
assert error1["message"] =~ ~s(Field transactions is too complex)
@@ -465,7 +465,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressTest do
"count" => 9
}
- conn = post(conn, "/graphql", query: query, variables: variables)
+ conn = post(conn, "/api/v1/graphql", query: query, variables: variables)
%{
"data" => %{
@@ -525,7 +525,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressTest do
"first" => 3
}
- conn = post(conn, "/graphql", query: query1, variables: variables1)
+ conn = post(conn, "/api/v1/graphql", query: query1, variables: variables1)
%{"data" => %{"address" => %{"transactions" => page1}}} = json_response(conn, 200)
@@ -564,7 +564,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressTest do
"after" => last_cursor_page1
}
- conn = post(conn, "/graphql", query: query2, variables: variables2)
+ conn = post(conn, "/api/v1/graphql", query: query2, variables: variables2)
%{"data" => %{"address" => %{"transactions" => page2}}} = json_response(conn, 200)
@@ -583,7 +583,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressTest do
"after" => last_cursor_page2
}
- conn = post(conn, "/graphql", query: query2, variables: variables3)
+ conn = post(conn, "/api/v1/graphql", query: query2, variables: variables3)
%{"data" => %{"address" => %{"transactions" => page3}}} = json_response(conn, 200)
diff --git a/apps/block_scout_web/test/block_scout_web/schema/query/addresses_test.exs b/apps/block_scout_web/test/block_scout_web/schema/query/addresses_test.exs
index f6145a4afc1d..e3ca744f4937 100644
--- a/apps/block_scout_web/test/block_scout_web/schema/query/addresses_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/schema/query/addresses_test.exs
@@ -18,7 +18,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressesTest do
variables = %{"hashes" => to_string(address.hash)}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert json_response(conn, 200) == %{
"data" => %{
@@ -47,7 +47,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressesTest do
variables = %{"hashes" => to_string(address.hash)}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert json_response(conn, 200) == %{
"data" => %{
@@ -82,7 +82,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressesTest do
variables = %{"hashes" => to_string(address.hash)}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert json_response(conn, 200) == %{
"data" => %{
@@ -116,7 +116,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressesTest do
variables = %{"hashes" => [to_string(address.hash)]}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert %{"errors" => [error]} = json_response(conn, 200)
assert error["message"] =~ ~s(Addresses not found.)
@@ -133,7 +133,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressesTest do
variables = %{}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert %{"errors" => [error]} = json_response(conn, 200)
assert error["message"] == ~s(In argument "hashes": Expected type "[AddressHash!]!", found null.)
@@ -150,7 +150,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressesTest do
variables = %{"hashes" => ["someInvalidHash"]}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert %{"errors" => [error]} = json_response(conn, 200)
assert error["message"] =~ ~s(Argument "hashes" has invalid value)
@@ -175,7 +175,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressesTest do
variables = %{"hashes" => hashes}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert %{"errors" => [error1, error2]} = json_response(conn, 200)
assert error1["message"] =~ ~s(Field addresses is too complex)
diff --git a/apps/block_scout_web/test/block_scout_web/schema/query/block_test.exs b/apps/block_scout_web/test/block_scout_web/schema/query/block_test.exs
index d601e3dd1005..26c612b344ce 100644
--- a/apps/block_scout_web/test/block_scout_web/schema/query/block_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/schema/query/block_test.exs
@@ -27,7 +27,7 @@ defmodule BlockScoutWeb.Schema.Query.BlockTest do
variables = %{"number" => block.number}
- conn = post(conn, "/graphql", query: query, variables: variables)
+ conn = post(conn, "/api/v1/graphql", query: query, variables: variables)
assert json_response(conn, 200) == %{
"data" => %{
@@ -63,7 +63,7 @@ defmodule BlockScoutWeb.Schema.Query.BlockTest do
variables = %{"number" => non_existent_block_number}
- conn = post(conn, "/graphql", query: query, variables: variables)
+ conn = post(conn, "/api/v1/graphql", query: query, variables: variables)
assert %{"errors" => [error]} = json_response(conn, 200)
assert error["message"] =~ ~s(Block number #{non_existent_block_number} was not found)
@@ -80,7 +80,7 @@ defmodule BlockScoutWeb.Schema.Query.BlockTest do
}
"""
- conn = get(conn, "/graphql", query: query)
+ conn = get(conn, "/api/v1/graphql", query: query)
assert %{"errors" => [error]} = json_response(conn, 200)
assert error["message"] == ~s(In argument "number": Expected type "Int!", found null.)
@@ -99,7 +99,7 @@ defmodule BlockScoutWeb.Schema.Query.BlockTest do
variables = %{"number" => "invalid"}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert %{"errors" => [error]} = json_response(conn, 200)
assert error["message"] =~ ~s(Argument "number" has invalid value)
diff --git a/apps/block_scout_web/test/block_scout_web/schema/query/node_test.exs b/apps/block_scout_web/test/block_scout_web/schema/query/node_test.exs
index 4638e2f1c379..75e6aacd1ac6 100644
--- a/apps/block_scout_web/test/block_scout_web/schema/query/node_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/schema/query/node_test.exs
@@ -20,7 +20,7 @@ defmodule BlockScoutWeb.Schema.Query.NodeTest do
variables = %{"id" => id}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert json_response(conn, 200) == %{
"data" => %{
@@ -50,7 +50,7 @@ defmodule BlockScoutWeb.Schema.Query.NodeTest do
variables = %{"id" => id}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
%{"errors" => [error]} = json_response(conn, 200)
@@ -88,7 +88,7 @@ defmodule BlockScoutWeb.Schema.Query.NodeTest do
variables = %{"id" => id}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert json_response(conn, 200) == %{
"data" => %{
@@ -132,7 +132,7 @@ defmodule BlockScoutWeb.Schema.Query.NodeTest do
variables = %{"id" => id}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
%{"errors" => [error]} = json_response(conn, 200)
@@ -163,7 +163,7 @@ defmodule BlockScoutWeb.Schema.Query.NodeTest do
variables = %{"id" => id}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert json_response(conn, 200) == %{
"data" => %{
@@ -199,7 +199,7 @@ defmodule BlockScoutWeb.Schema.Query.NodeTest do
variables = %{"id" => id}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
%{"errors" => [error]} = json_response(conn, 200)
diff --git a/apps/block_scout_web/test/block_scout_web/schema/query/token_transfers_test.exs b/apps/block_scout_web/test/block_scout_web/schema/query/token_transfers_test.exs
index aef875fe3e16..d10a3fdcc639 100644
--- a/apps/block_scout_web/test/block_scout_web/schema/query/token_transfers_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/schema/query/token_transfers_test.exs
@@ -16,7 +16,6 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do
amounts
block_number
log_index
- token_id
token_ids
from_address_hash
to_address_hash
@@ -33,7 +32,7 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do
"first" => 1
}
- conn = post(conn, "/graphql", query: query, variables: variables)
+ conn = post(conn, "/api/v1/graphql", query: query, variables: variables)
assert json_response(conn, 200) == %{
"data" => %{
@@ -45,7 +44,6 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do
"amounts" => Enum.map(token_transfer.amounts, &to_string/1),
"block_number" => token_transfer.block_number,
"log_index" => token_transfer.log_index,
- "token_id" => token_transfer.token_id,
"token_ids" => Enum.map(token_transfer.token_ids, &to_string/1),
"from_address_hash" => to_string(token_transfer.from_address_hash),
"to_address_hash" => to_string(token_transfer.to_address_hash),
@@ -70,7 +68,6 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do
amount
block_number
log_index
- token_id
from_address_hash
to_address_hash
token_contract_address_hash
@@ -86,7 +83,7 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do
"first" => 10
}
- conn = post(conn, "/graphql", query: query, variables: variables)
+ conn = post(conn, "/api/v1/graphql", query: query, variables: variables)
assert json_response(conn, 200) == %{
"data" => %{
@@ -121,7 +118,7 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do
response1 =
conn
- |> post("/graphql", query: query1, variables: variables1)
+ |> post("/api/v1/graphql", query: query1, variables: variables1)
|> json_response(200)
%{"errors" => [response1_error1, response1_error2]} = response1
@@ -150,7 +147,7 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do
response2 =
conn
- |> post("/graphql", query: query2, variables: variables2)
+ |> post("/api/v1/graphql", query: query2, variables: variables2)
|> json_response(200)
%{"errors" => [response2_error1, response2_error2]} = response2
@@ -212,7 +209,7 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do
[token_transfer] =
conn
- |> post("/graphql", query: query, variables: variables)
+ |> post("/api/v1/graphql", query: query, variables: variables)
|> json_response(200)
|> get_in(["data", "token_transfers", "edges"])
@@ -264,7 +261,7 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do
"first" => 1
}
- conn = post(conn, "/graphql", query: query1, variables: variables1)
+ conn = post(conn, "/api/v1/graphql", query: query1, variables: variables1)
%{"data" => %{"token_transfers" => page1}} = json_response(conn, 200)
@@ -300,7 +297,7 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do
"after" => last_cursor_page1
}
- conn = post(conn, "/graphql", query: query2, variables: variables2)
+ conn = post(conn, "/api/v1/graphql", query: query2, variables: variables2)
%{"data" => %{"token_transfers" => page2}} = json_response(conn, 200)
@@ -319,7 +316,7 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do
"after" => last_cursor_page2
}
- conn = post(conn, "/graphql", query: query2, variables: variables3)
+ conn = post(conn, "/api/v1/graphql", query: query2, variables: variables3)
%{"data" => %{"token_transfers" => page3}} = json_response(conn, 200)
diff --git a/apps/block_scout_web/test/block_scout_web/schema/query/transaction_test.exs b/apps/block_scout_web/test/block_scout_web/schema/query/transaction_test.exs
index 66b31d676c72..6212ca89a788 100644
--- a/apps/block_scout_web/test/block_scout_web/schema/query/transaction_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/schema/query/transaction_test.exs
@@ -37,7 +37,7 @@ defmodule BlockScoutWeb.Schema.Query.TransactionTest do
variables = %{"hash" => to_string(transaction.hash)}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert json_response(conn, 200) == %{
"data" => %{
@@ -78,7 +78,7 @@ defmodule BlockScoutWeb.Schema.Query.TransactionTest do
variables = %{"hash" => to_string(transaction.hash)}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert %{"errors" => [error]} = json_response(conn, 200)
assert error["message"] == "Transaction not found."
@@ -93,7 +93,7 @@ defmodule BlockScoutWeb.Schema.Query.TransactionTest do
}
"""
- conn = get(conn, "/graphql", query: query)
+ conn = get(conn, "/api/v1/graphql", query: query)
assert %{"errors" => [error]} = json_response(conn, 200)
assert error["message"] == ~s(In argument "hash": Expected type "FullHash!", found null.)
@@ -110,7 +110,7 @@ defmodule BlockScoutWeb.Schema.Query.TransactionTest do
variables = %{"hash" => "0x000"}
- conn = get(conn, "/graphql", query: query, variables: variables)
+ conn = get(conn, "/api/v1/graphql", query: query, variables: variables)
assert %{"errors" => [error]} = json_response(conn, 200)
assert error["message"] =~ ~s(Argument "hash" has invalid value)
@@ -180,7 +180,7 @@ defmodule BlockScoutWeb.Schema.Query.TransactionTest do
"first" => 1
}
- conn = post(conn, "/graphql", query: query, variables: variables)
+ conn = post(conn, "/api/v1/graphql", query: query, variables: variables)
assert json_response(conn, 200) == %{
"data" => %{
@@ -247,7 +247,7 @@ defmodule BlockScoutWeb.Schema.Query.TransactionTest do
"first" => 1
}
- conn = post(conn, "/graphql", query: query, variables: variables)
+ conn = post(conn, "/api/v1/graphql", query: query, variables: variables)
assert json_response(conn, 200) == %{
"data" => %{
@@ -306,7 +306,7 @@ defmodule BlockScoutWeb.Schema.Query.TransactionTest do
response =
conn
- |> post("/graphql", query: query, variables: variables)
+ |> post("/api/v1/graphql", query: query, variables: variables)
|> json_response(200)
internal_transactions = get_in(response, ["data", "transaction", "internal_transactions", "edges"])
@@ -341,7 +341,7 @@ defmodule BlockScoutWeb.Schema.Query.TransactionTest do
response1 =
conn
- |> post("/graphql", query: query1, variables: variables1)
+ |> post("/api/v1/graphql", query: query1, variables: variables1)
|> json_response(200)
assert %{"errors" => [error1, error2, error3]} = response1
@@ -372,7 +372,7 @@ defmodule BlockScoutWeb.Schema.Query.TransactionTest do
response2 =
conn
- |> post("/graphql", query: query2, variables: variables2)
+ |> post("/api/v1/graphql", query: query2, variables: variables2)
|> json_response(200)
assert %{"errors" => [error1, error2, error3]} = response2
@@ -435,7 +435,7 @@ defmodule BlockScoutWeb.Schema.Query.TransactionTest do
[internal_transaction] =
conn
- |> post("/graphql", query: query, variables: variables)
+ |> post("/api/v1/graphql", query: query, variables: variables)
|> json_response(200)
|> get_in(["data", "transaction", "internal_transactions", "edges"])
@@ -479,7 +479,7 @@ defmodule BlockScoutWeb.Schema.Query.TransactionTest do
"first" => 2
}
- conn = post(conn, "/graphql", query: query1, variables: variables1)
+ conn = post(conn, "/api/v1/graphql", query: query1, variables: variables1)
%{"data" => %{"transaction" => %{"internal_transactions" => page1}}} = json_response(conn, 200)
@@ -520,7 +520,7 @@ defmodule BlockScoutWeb.Schema.Query.TransactionTest do
page2 =
conn
- |> post("/graphql", query: query2, variables: variables2)
+ |> post("/api/v1/graphql", query: query2, variables: variables2)
|> json_response(200)
|> get_in(["data", "transaction", "internal_transactions"])
@@ -541,7 +541,7 @@ defmodule BlockScoutWeb.Schema.Query.TransactionTest do
page3 =
conn
- |> post("/graphql", query: query2, variables: variables3)
+ |> post("/api/v1/graphql", query: query2, variables: variables3)
|> json_response(200)
|> get_in(["data", "transaction", "internal_transactions"])
diff --git a/apps/block_scout_web/test/block_scout_web/views/address_token_balance_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/address_token_balance_view_test.exs
index eb9fa83061d8..778c1eb0364b 100644
--- a/apps/block_scout_web/test/block_scout_web/views/address_token_balance_view_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/views/address_token_balance_view_test.exs
@@ -20,11 +20,9 @@ defmodule BlockScoutWeb.AddressTokenBalanceViewTest do
token_balance_a = build(:token_balance, token: build(:token, type: "ERC-20"))
token_balance_b = build(:token_balance, token: build(:token, type: "ERC-721"))
- token_balances = [{token_balance_a, token_balance_a.token}, {token_balance_b, token_balance_b.token}]
+ token_balances = [token_balance_a, token_balance_b]
- assert AddressTokenBalanceView.filter_by_type(token_balances, "ERC-20") == [
- {token_balance_a, token_balance_a.token}
- ]
+ assert AddressTokenBalanceView.filter_by_type(token_balances, "ERC-20") == [token_balance_a]
end
end
diff --git a/apps/block_scout_web/test/block_scout_web/views/api/v2/address_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/api/v2/address_view_test.exs
new file mode 100644
index 000000000000..7177316bc125
--- /dev/null
+++ b/apps/block_scout_web/test/block_scout_web/views/api/v2/address_view_test.exs
@@ -0,0 +1,76 @@
+defmodule BlockScoutWeb.API.V2.AddressViewTest do
+ use BlockScoutWeb.ConnCase, async: true
+
+ import Mox
+
+ alias BlockScoutWeb.API.V2.AddressView
+ alias Explorer.Repo
+
+ test "for a proxy contract has_methods_read_proxy is true" do
+ implementation_address = insert(:contract_address)
+ proxy_address = insert(:contract_address) |> Repo.preload([:token])
+
+ _proxy_smart_contract =
+ insert(:smart_contract,
+ address_hash: proxy_address.hash,
+ contract_code_md5: "123",
+ implementation_address_hash: implementation_address.hash
+ )
+
+ get_eip1967_implementation_zero_addresses()
+
+ assert AddressView.prepare_address(proxy_address)["has_methods_read_proxy"] == true
+ end
+
+ def get_eip1967_implementation_zero_addresses do
+ EthereumJSONRPC.Mox
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
+ end
+end
diff --git a/apps/block_scout_web/test/block_scout_web/views/smart_contract_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/smart_contract_view_test.exs
index 7ba53f18ced2..cb63435730b2 100644
--- a/apps/block_scout_web/test/block_scout_web/views/smart_contract_view_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/views/smart_contract_view_test.exs
@@ -8,47 +8,32 @@ defmodule BlockScoutWeb.SmartContractViewTest do
describe "values_with_type/1" do
test "complex data type case" do
value =
- {<<156, 209, 70, 119, 249, 170, 85, 105, 179, 187, 179, 81, 252, 214, 125, 17, 21, 170, 86, 58, 225, 98, 66,
- 118, 211, 212, 230, 127, 179, 214, 249, 38>>, 23_183_417, true,
+ {"0x9cd14677f9aa5569b3bbb351fcd67d1115aa563ae1624276d3d4e67fb3d6f926", 23_183_417, true,
[
{<<164, 118, 64, 69, 133, 31, 23, 170, 96, 182, 200, 232, 182, 32, 114, 190, 169, 83, 133, 33>>,
[
- <<15, 103, 152, 165, 96, 121, 58, 84, 195, 188, 254, 134, 169, 60, 222, 30, 115, 8, 125, 148, 76, 14, 162,
- 5, 68, 19, 125, 65, 33, 57, 104, 133>>,
- <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191, 61, 111, 131, 12, 226, 99, 202, 233, 135, 25, 57, 130, 25, 44,
- 217, 144, 68, 43, 83>>
- ],
- <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 178, 96, 212, 241, 78, 0, 0>>},
+ "0x0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d4121396885",
+ "0x000000000000000000000000bf3d6f830ce263cae987193982192cd990442b53"
+ ], "0x000000000000000000000000000000000000000000000000aab260d4f14e0000"},
{<<164, 118, 64, 69, 133, 31, 23, 170, 96, 182, 200, 232, 182, 32, 114, 190, 169, 83, 133, 33>>,
[
- <<221, 242, 82, 173, 27, 226, 200, 155, 105, 194, 176, 104, 252, 55, 141, 170, 149, 43, 167, 241, 99, 196,
- 161, 22, 40, 245, 90, 77, 245, 35, 179, 239>>,
- <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>,
- <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191, 61, 111, 131, 12, 226, 99, 202, 233, 135, 25, 57, 130, 25, 44,
- 217, 144, 68, 43, 83>>
- ],
- <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 178, 96, 212, 241, 78, 0, 0>>},
+ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0x000000000000000000000000bf3d6f830ce263cae987193982192cd990442b53"
+ ], "0x000000000000000000000000000000000000000000000000aab260d4f14e0000"},
{<<166, 139, 214, 89, 169, 22, 127, 61, 60, 1, 186, 151, 118, 161, 32, 141, 174, 143, 0, 59>>,
[
- <<47, 154, 96, 152, 212, 80, 58, 18, 119, 121, 186, 151, 95, 95, 107, 4, 248, 66, 54, 43, 24, 9, 243, 70,
- 152, 158, 154, 188, 11, 77, 237, 182>>,
- <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191, 61, 111, 131, 12, 226, 99, 202, 233, 135, 25, 57, 130, 25, 44,
- 217, 144, 68, 43, 83>>,
- <<0, 5, 0, 0, 36, 155, 252, 47, 60, 200, 214, 143, 107, 107, 247, 35, 14, 160, 168, 237, 133, 61, 231, 49,
- 0, 0, 0, 0, 0, 0, 2, 79>>
- ],
- <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 178, 96, 212, 241, 78, 0, 0>>},
+ "0x2f9a6098d4503a127779ba975f5f6b04f842362b1809f346989e9abc0b4dedb6",
+ "0x000000000000000000000000bf3d6f830ce263cae987193982192cd990442b53",
+ "0x00050000249bfc2f3cc8d68f6b6bf7230ea0a8ed853de731000000000000024f"
+ ], "0x000000000000000000000000000000000000000000000000aab260d4f14e0000"},
{<<254, 68, 107, 239, 29, 191, 122, 254, 36, 232, 30, 5, 188, 139, 39, 28, 27, 169, 165, 96>>,
[
- <<39, 51, 62, 219, 139, 220, 212, 10, 10, 233, 68, 251, 18, 27, 94, 45, 98, 234, 120, 38, 131, 148, 102,
- 84, 160, 245, 230, 7, 169, 8, 213, 120>>,
- <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 95, 197, 45, 138, 86, 59, 47, 24, 28, 106, 82, 125, 66, 46, 21,
- 146, 201, 236, 250>>,
- <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 166, 139, 214, 89, 169, 22, 127, 61, 60, 1, 186, 151, 118, 161, 32,
- 141, 174, 143, 0, 59>>,
- <<0, 5, 0, 0, 36, 155, 252, 47, 60, 200, 214, 143, 107, 107, 247, 35, 14, 160, 168, 237, 133, 61, 231, 49,
- 0, 0, 0, 0, 0, 0, 2, 79>>
- ], <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1>>}
+ "0x27333edb8bdcd40a0ae944fb121b5e2d62ea782683946654a0f5e607a908d578",
+ "0x0000000000000000000000002a5fc52d8a563b2f181c6a527d422e1592c9ecfa",
+ "0x000000000000000000000000a68bd659a9167f3d3c01ba9776a1208dae8f003b",
+ "0x00050000249bfc2f3cc8d68f6b6bf7230ea0a8ed853de731000000000000024f"
+ ], "0x0000000000000000000000000000000000000000000000000000000000000001"}
]}
type = "tuple[bytes32,uint256,bool,tuple[address,bytes32[],bytes][]]"
diff --git a/apps/block_scout_web/test/block_scout_web/views/tokens/helper_test.exs b/apps/block_scout_web/test/block_scout_web/views/tokens/helper_test.exs
index e59a85fb76c4..a8815379cedb 100644
--- a/apps/block_scout_web/test/block_scout_web/views/tokens/helper_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/views/tokens/helper_test.exs
@@ -27,7 +27,7 @@ defmodule BlockScoutWeb.Tokens.HelperTest do
test "returns a string with the token_id with ERC-721 token" do
token = build(:token, type: "ERC-721", decimals: nil)
- token_transfer = build(:token_transfer, token: token, amount: nil, token_id: 1)
+ token_transfer = build(:token_transfer, token: token, amount: nil, token_ids: [1])
assert Helper.token_transfer_amount(token_transfer) == {:ok, :erc721_instance}
end
diff --git a/apps/block_scout_web/test/support/fixture/smart_contract/eth_bytecode_db_search_all_local_sources_response.json b/apps/block_scout_web/test/support/fixture/smart_contract/eth_bytecode_db_search_all_local_sources_response.json
new file mode 100644
index 000000000000..69d1e1876b84
--- /dev/null
+++ b/apps/block_scout_web/test/support/fixture/smart_contract/eth_bytecode_db_search_all_local_sources_response.json
@@ -0,0 +1,59 @@
+{
+ "ethBytecodeDbSources": [
+ {
+ "fileName": "contracts/uniswapv2/UniswapV2Router02.sol",
+ "contractName": "UniswapV2Router02",
+ "compilerVersion": "v0.6.12+commit.27d51765",
+ "compilerSettings": "{\"libraries\":{\"\":{\"__CACHE_BREAKER__\":\"0x00000000d41867734bbee4c6863d9255b2b06ac1\"}},\"metadata\":{\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"outputSelection\":{\"*\":{\"*\":[\"abi\",\"evm.bytecode\",\"evm.deployedBytecode\",\"evm.methodIdentifiers\"]}}}",
+ "sourceType": "SOLIDITY",
+ "sourceFiles": {
+ "@openzeppelin/contracts/access/Ownable.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.6.0 <0.8.0;\n\nimport \"../utils/Context.sol\";\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor () internal {\n address msgSender = _msgSender();\n _owner = msgSender;\n emit OwnershipTransferred(address(0), msgSender);\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n _;\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions anymore. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby removing any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n emit OwnershipTransferred(_owner, address(0));\n _owner = address(0);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n emit OwnershipTransferred(_owner, newOwner);\n _owner = newOwner;\n }\n}\n",
+ "@openzeppelin/contracts/math/SafeMath.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.6.0 <0.8.0;\n\n/**\n * @dev Wrappers over Solidity's arithmetic operations with added overflow\n * checks.\n *\n * Arithmetic operations in Solidity wrap on overflow. This can easily result\n * in bugs, because programmers usually assume that an overflow raises an\n * error, which is the standard behavior in high level programming languages.\n * `SafeMath` restores this intuition by reverting the transaction when an\n * operation overflows.\n *\n * Using this library instead of the unchecked operations eliminates an entire\n * class of bugs, so it's recommended to use it always.\n */\nlibrary SafeMath {\n /**\n * @dev Returns the addition of two unsigned integers, with an overflow flag.\n *\n * _Available since v3.4._\n */\n function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n uint256 c = a + b;\n if (c < a) return (false, 0);\n return (true, c);\n }\n\n /**\n * @dev Returns the substraction of two unsigned integers, with an overflow flag.\n *\n * _Available since v3.4._\n */\n function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n if (b > a) return (false, 0);\n return (true, a - b);\n }\n\n /**\n * @dev Returns the multiplication of two unsigned integers, with an overflow flag.\n *\n * _Available since v3.4._\n */\n function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n // Gas optimization: this is cheaper than requiring 'a' not being zero, but the\n // benefit is lost if 'b' is also tested.\n // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522\n if (a == 0) return (true, 0);\n uint256 c = a * b;\n if (c / a != b) return (false, 0);\n return (true, c);\n }\n\n /**\n * @dev Returns the division of two unsigned integers, with a division by zero flag.\n *\n * _Available since v3.4._\n */\n function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n if (b == 0) return (false, 0);\n return (true, a / b);\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.\n *\n * _Available since v3.4._\n */\n function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n if (b == 0) return (false, 0);\n return (true, a % b);\n }\n\n /**\n * @dev Returns the addition of two unsigned integers, reverting on\n * overflow.\n *\n * Counterpart to Solidity's `+` operator.\n *\n * Requirements:\n *\n * - Addition cannot overflow.\n */\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\n uint256 c = a + b;\n require(c >= a, \"SafeMath: addition overflow\");\n return c;\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, reverting on\n * overflow (when the result is negative).\n *\n * Counterpart to Solidity's `-` operator.\n *\n * Requirements:\n *\n * - Subtraction cannot overflow.\n */\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\n require(b <= a, \"SafeMath: subtraction overflow\");\n return a - b;\n }\n\n /**\n * @dev Returns the multiplication of two unsigned integers, reverting on\n * overflow.\n *\n * Counterpart to Solidity's `*` operator.\n *\n * Requirements:\n *\n * - Multiplication cannot overflow.\n */\n function mul(uint256 a, uint256 b) internal pure returns (uint256) {\n if (a == 0) return 0;\n uint256 c = a * b;\n require(c / a == b, \"SafeMath: multiplication overflow\");\n return c;\n }\n\n /**\n * @dev Returns the integer division of two unsigned integers, reverting on\n * division by zero. The result is rounded towards zero.\n *\n * Counterpart to Solidity's `/` operator. Note: this function uses a\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\n * uses an invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function div(uint256 a, uint256 b) internal pure returns (uint256) {\n require(b > 0, \"SafeMath: division by zero\");\n return a / b;\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\n * reverting when dividing by zero.\n *\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\n * opcode (which leaves remaining gas untouched) while Solidity uses an\n * invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function mod(uint256 a, uint256 b) internal pure returns (uint256) {\n require(b > 0, \"SafeMath: modulo by zero\");\n return a % b;\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, reverting with custom message on\n * overflow (when the result is negative).\n *\n * CAUTION: This function is deprecated because it requires allocating memory for the error\n * message unnecessarily. For custom revert reasons use {trySub}.\n *\n * Counterpart to Solidity's `-` operator.\n *\n * Requirements:\n *\n * - Subtraction cannot overflow.\n */\n function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\n require(b <= a, errorMessage);\n return a - b;\n }\n\n /**\n * @dev Returns the integer division of two unsigned integers, reverting with custom message on\n * division by zero. The result is rounded towards zero.\n *\n * CAUTION: This function is deprecated because it requires allocating memory for the error\n * message unnecessarily. For custom revert reasons use {tryDiv}.\n *\n * Counterpart to Solidity's `/` operator. Note: this function uses a\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\n * uses an invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\n require(b > 0, errorMessage);\n return a / b;\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\n * reverting with custom message when dividing by zero.\n *\n * CAUTION: This function is deprecated because it requires allocating memory for the error\n * message unnecessarily. For custom revert reasons use {tryMod}.\n *\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\n * opcode (which leaves remaining gas untouched) while Solidity uses an\n * invalid opcode to revert (consuming all remaining gas).\n *\n * Requirements:\n *\n * - The divisor cannot be zero.\n */\n function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\n require(b > 0, errorMessage);\n return a % b;\n }\n}\n",
+ "@openzeppelin/contracts/token/ERC20/ERC20.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.6.0 <0.8.0;\n\nimport \"../../utils/Context.sol\";\nimport \"./IERC20.sol\";\nimport \"../../math/SafeMath.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n * For a generic mechanism see {ERC20PresetMinterPauser}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * We have followed general OpenZeppelin guidelines: functions revert instead\n * of returning `false` on failure. This behavior is nonetheless conventional\n * and does not conflict with the expectations of ERC20 applications.\n *\n * Additionally, an {Approval} event is emitted on calls to {transferFrom}.\n * This allows applications to reconstruct the allowance for all accounts just\n * by listening to said events. Other implementations of the EIP may not emit\n * these events, as it isn't required by the specification.\n *\n * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}\n * functions have been added to mitigate the well-known issues around setting\n * allowances. See {IERC20-approve}.\n */\ncontract ERC20 is Context, IERC20 {\n using SafeMath for uint256;\n\n mapping (address => uint256) private _balances;\n\n mapping (address => mapping (address => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n uint8 private _decimals;\n\n /**\n * @dev Sets the values for {name} and {symbol}, initializes {decimals} with\n * a default value of 18.\n *\n * To select a different value for {decimals}, use {_setupDecimals}.\n *\n * All three of these values are immutable: they can only be set once during\n * construction.\n */\n constructor (string memory name_, string memory symbol_) public {\n _name = name_;\n _symbol = symbol_;\n _decimals = 18;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5,05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is\n * called.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual returns (uint8) {\n return _decimals;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view virtual override returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view virtual override returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `recipient` cannot be the zero address.\n * - the caller must have a balance of at least `amount`.\n */\n function transfer(address recipient, uint256 amount) public virtual override returns (bool) {\n _transfer(_msgSender(), recipient, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view virtual override returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 amount) public virtual override returns (bool) {\n _approve(_msgSender(), spender, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Emits an {Approval} event indicating the updated allowance. This is not\n * required by the EIP. See the note at the beginning of {ERC20}.\n *\n * Requirements:\n *\n * - `sender` and `recipient` cannot be the zero address.\n * - `sender` must have a balance of at least `amount`.\n * - the caller must have allowance for ``sender``'s tokens of at least\n * `amount`.\n */\n function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {\n _transfer(sender, recipient, amount);\n _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, \"ERC20: transfer amount exceeds allowance\"));\n return true;\n }\n\n /**\n * @dev Atomically increases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {\n _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));\n return true;\n }\n\n /**\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `spender` must have allowance for the caller of at least\n * `subtractedValue`.\n */\n function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {\n _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, \"ERC20: decreased allowance below zero\"));\n return true;\n }\n\n /**\n * @dev Moves tokens `amount` from `sender` to `recipient`.\n *\n * This is internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * Requirements:\n *\n * - `sender` cannot be the zero address.\n * - `recipient` cannot be the zero address.\n * - `sender` must have a balance of at least `amount`.\n */\n function _transfer(address sender, address recipient, uint256 amount) internal virtual {\n require(sender != address(0), \"ERC20: transfer from the zero address\");\n require(recipient != address(0), \"ERC20: transfer to the zero address\");\n\n _beforeTokenTransfer(sender, recipient, amount);\n\n _balances[sender] = _balances[sender].sub(amount, \"ERC20: transfer amount exceeds balance\");\n _balances[recipient] = _balances[recipient].add(amount);\n emit Transfer(sender, recipient, amount);\n }\n\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\n * the total supply.\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n */\n function _mint(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: mint to the zero address\");\n\n _beforeTokenTransfer(address(0), account, amount);\n\n _totalSupply = _totalSupply.add(amount);\n _balances[account] = _balances[account].add(amount);\n emit Transfer(address(0), account, amount);\n }\n\n /**\n * @dev Destroys `amount` tokens from `account`, reducing the\n * total supply.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n * - `account` must have at least `amount` tokens.\n */\n function _burn(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: burn from the zero address\");\n\n _beforeTokenTransfer(account, address(0), amount);\n\n _balances[account] = _balances[account].sub(amount, \"ERC20: burn amount exceeds balance\");\n _totalSupply = _totalSupply.sub(amount);\n emit Transfer(account, address(0), amount);\n }\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n */\n function _approve(address owner, address spender, uint256 amount) internal virtual {\n require(owner != address(0), \"ERC20: approve from the zero address\");\n require(spender != address(0), \"ERC20: approve to the zero address\");\n\n _allowances[owner][spender] = amount;\n emit Approval(owner, spender, amount);\n }\n\n /**\n * @dev Sets {decimals} to a value other than the default one of 18.\n *\n * WARNING: This function should only be called from the constructor. Most\n * applications that interact with token contracts will not expect\n * {decimals} to ever change, and may work incorrectly if it does.\n */\n function _setupDecimals(uint8 decimals_) internal virtual {\n _decimals = decimals_;\n }\n\n /**\n * @dev Hook that is called before any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * will be to transferred to `to`.\n * - when `from` is zero, `amount` tokens will be minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }\n}\n",
+ "@openzeppelin/contracts/token/ERC20/IERC20.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.6.0 <0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address recipient, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);\n\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n",
+ "@openzeppelin/contracts/token/ERC20/SafeERC20.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.6.0 <0.8.0;\n\nimport \"./IERC20.sol\";\nimport \"../../math/SafeMath.sol\";\nimport \"../../utils/Address.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n using SafeMath for uint256;\n using Address for address;\n\n function safeTransfer(IERC20 token, address to, uint256 value) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\n }\n\n function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\n }\n\n /**\n * @dev Deprecated. This function has issues similar to the ones found in\n * {IERC20-approve}, and its usage is discouraged.\n *\n * Whenever possible, use {safeIncreaseAllowance} and\n * {safeDecreaseAllowance} instead.\n */\n function safeApprove(IERC20 token, address spender, uint256 value) internal {\n // safeApprove should only be called when setting an initial allowance,\n // or when resetting it to zero. To increase and decrease it, use\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\n // solhint-disable-next-line max-line-length\n require((value == 0) || (token.allowance(address(this), spender) == 0),\n \"SafeERC20: approve from non-zero to non-zero allowance\"\n );\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\n }\n\n function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {\n uint256 newAllowance = token.allowance(address(this), spender).add(value);\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n\n function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {\n uint256 newAllowance = token.allowance(address(this), spender).sub(value, \"SafeERC20: decreased allowance below zero\");\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n */\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\n // the target address contains contract code and also asserts for success in the low-level call.\n\n bytes memory returndata = address(token).functionCall(data, \"SafeERC20: low-level call failed\");\n if (returndata.length > 0) { // Return data is optional\n // solhint-disable-next-line max-line-length\n require(abi.decode(returndata, (bool)), \"SafeERC20: ERC20 operation did not succeed\");\n }\n }\n}\n",
+ "@openzeppelin/contracts/utils/Address.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.6.2 <0.8.0;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize, which returns 0 for contracts in\n // construction, since the code is only stored at the end of the\n // constructor execution.\n\n uint256 size;\n // solhint-disable-next-line no-inline-assembly\n assembly { size := extcodesize(account) }\n return size > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n // solhint-disable-next-line avoid-low-level-calls, avoid-call-value\n (bool success, ) = recipient.call{ value: amount }(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain`call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n require(isContract(target), \"Address: call to non-contract\");\n\n // solhint-disable-next-line avoid-low-level-calls\n (bool success, bytes memory returndata) = target.call{ value: value }(data);\n return _verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {\n require(isContract(target), \"Address: static call to non-contract\");\n\n // solhint-disable-next-line avoid-low-level-calls\n (bool success, bytes memory returndata) = target.staticcall(data);\n return _verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {\n require(isContract(target), \"Address: delegate call to non-contract\");\n\n // solhint-disable-next-line avoid-low-level-calls\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return _verifyCallResult(success, returndata, errorMessage);\n }\n\n function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n\n // solhint-disable-next-line no-inline-assembly\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n",
+ "@openzeppelin/contracts/utils/Context.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.6.0 <0.8.0;\n\n/*\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with GSN meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address payable) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes memory) {\n this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691\n return msg.data;\n }\n}\n",
+ "@openzeppelin/contracts/utils/EnumerableSet.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.6.0 <0.8.0;\n\n/**\n * @dev Library for managing\n * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive\n * types.\n *\n * Sets have the following properties:\n *\n * - Elements are added, removed, and checked for existence in constant time\n * (O(1)).\n * - Elements are enumerated in O(n). No guarantees are made on the ordering.\n *\n * ```\n * contract Example {\n * // Add the library methods\n * using EnumerableSet for EnumerableSet.AddressSet;\n *\n * // Declare a set state variable\n * EnumerableSet.AddressSet private mySet;\n * }\n * ```\n *\n * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)\n * and `uint256` (`UintSet`) are supported.\n */\nlibrary EnumerableSet {\n // To implement this library for multiple types with as little code\n // repetition as possible, we write it in terms of a generic Set type with\n // bytes32 values.\n // The Set implementation uses private functions, and user-facing\n // implementations (such as AddressSet) are just wrappers around the\n // underlying Set.\n // This means that we can only create new EnumerableSets for types that fit\n // in bytes32.\n\n struct Set {\n // Storage of set values\n bytes32[] _values;\n\n // Position of the value in the `values` array, plus 1 because index 0\n // means a value is not in the set.\n mapping (bytes32 => uint256) _indexes;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function _add(Set storage set, bytes32 value) private returns (bool) {\n if (!_contains(set, value)) {\n set._values.push(value);\n // The value is stored at length-1, but we add 1 to all indexes\n // and use 0 as a sentinel value\n set._indexes[value] = set._values.length;\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function _remove(Set storage set, bytes32 value) private returns (bool) {\n // We read and store the value's index to prevent multiple reads from the same storage slot\n uint256 valueIndex = set._indexes[value];\n\n if (valueIndex != 0) { // Equivalent to contains(set, value)\n // To delete an element from the _values array in O(1), we swap the element to delete with the last one in\n // the array, and then remove the last element (sometimes called as 'swap and pop').\n // This modifies the order of the array, as noted in {at}.\n\n uint256 toDeleteIndex = valueIndex - 1;\n uint256 lastIndex = set._values.length - 1;\n\n // When the value to delete is the last one, the swap operation is unnecessary. However, since this occurs\n // so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement.\n\n bytes32 lastvalue = set._values[lastIndex];\n\n // Move the last value to the index where the value to delete is\n set._values[toDeleteIndex] = lastvalue;\n // Update the index for the moved value\n set._indexes[lastvalue] = toDeleteIndex + 1; // All indexes are 1-based\n\n // Delete the slot where the moved value was stored\n set._values.pop();\n\n // Delete the index for the deleted slot\n delete set._indexes[value];\n\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function _contains(Set storage set, bytes32 value) private view returns (bool) {\n return set._indexes[value] != 0;\n }\n\n /**\n * @dev Returns the number of values on the set. O(1).\n */\n function _length(Set storage set) private view returns (uint256) {\n return set._values.length;\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function _at(Set storage set, uint256 index) private view returns (bytes32) {\n require(set._values.length > index, \"EnumerableSet: index out of bounds\");\n return set._values[index];\n }\n\n // Bytes32Set\n\n struct Bytes32Set {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {\n return _add(set._inner, value);\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {\n return _remove(set._inner, value);\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {\n return _contains(set._inner, value);\n }\n\n /**\n * @dev Returns the number of values in the set. O(1).\n */\n function length(Bytes32Set storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {\n return _at(set._inner, index);\n }\n\n // AddressSet\n\n struct AddressSet {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(AddressSet storage set, address value) internal returns (bool) {\n return _add(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(AddressSet storage set, address value) internal returns (bool) {\n return _remove(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(AddressSet storage set, address value) internal view returns (bool) {\n return _contains(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Returns the number of values in the set. O(1).\n */\n function length(AddressSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(AddressSet storage set, uint256 index) internal view returns (address) {\n return address(uint160(uint256(_at(set._inner, index))));\n }\n\n\n // UintSet\n\n struct UintSet {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(UintSet storage set, uint256 value) internal returns (bool) {\n return _add(set._inner, bytes32(value));\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(UintSet storage set, uint256 value) internal returns (bool) {\n return _remove(set._inner, bytes32(value));\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(UintSet storage set, uint256 value) internal view returns (bool) {\n return _contains(set._inner, bytes32(value));\n }\n\n /**\n * @dev Returns the number of values on the set. O(1).\n */\n function length(UintSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(UintSet storage set, uint256 index) internal view returns (uint256) {\n return uint256(_at(set._inner, index));\n }\n}\n",
+ "contracts/MasterChef.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.6.12;\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/SafeERC20.sol\";\nimport \"@openzeppelin/contracts/utils/EnumerableSet.sol\";\nimport \"@openzeppelin/contracts/math/SafeMath.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\nimport \"./SushiToken.sol\";\n\ninterface IMigratorChef {\n // Perform LP token migration from legacy UniswapV2 to SushiSwap.\n // Take the current LP token address and return the new LP token address.\n // Migrator should have full access to the caller's LP token.\n // Return the new LP token address.\n //\n // XXX Migrator must have allowance access to UniswapV2 LP tokens.\n // SushiSwap must mint EXACTLY the same amount of SushiSwap LP tokens or\n // else something bad will happen. Traditional UniswapV2 does not\n // do that so be careful!\n function migrate(IERC20 token) external returns (IERC20);\n}\n\n// MasterChef is the master of Sushi. He can make Sushi and he is a fair guy.\n//\n// Note that it's ownable and the owner wields tremendous power. The ownership\n// will be transferred to a governance smart contract once SUSHI is sufficiently\n// distributed and the community can show to govern itself.\n//\n// Have fun reading it. Hopefully it's bug-free. God bless.\ncontract MasterChef is Ownable {\n using SafeMath for uint256;\n using SafeERC20 for IERC20;\n // Info of each user.\n struct UserInfo {\n uint256 amount; // How many LP tokens the user has provided.\n uint256 rewardDebt; // Reward debt. See explanation below.\n //\n // We do some fancy math here. Basically, any point in time, the amount of SUSHIs\n // entitled to a user but is pending to be distributed is:\n //\n // pending reward = (user.amount * pool.accSushiPerShare) - user.rewardDebt\n //\n // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:\n // 1. The pool's `accSushiPerShare` (and `lastRewardBlock`) gets updated.\n // 2. User receives the pending reward sent to his/her address.\n // 3. User's `amount` gets updated.\n // 4. User's `rewardDebt` gets updated.\n }\n // Info of each pool.\n struct PoolInfo {\n IERC20 lpToken; // Address of LP token contract.\n uint256 allocPoint; // How many allocation points assigned to this pool. SUSHIs to distribute per block.\n uint256 lastRewardBlock; // Last block number that SUSHIs distribution occurs.\n uint256 accSushiPerShare; // Accumulated SUSHIs per share, times 1e12. See below.\n }\n // The SUSHI TOKEN!\n SushiToken public sushi;\n // Dev address.\n address public devaddr;\n // Block number when bonus SUSHI period ends.\n uint256 public bonusEndBlock;\n // SUSHI tokens created per block.\n uint256 public sushiPerBlock;\n // Bonus muliplier for early sushi makers.\n uint256 public constant BONUS_MULTIPLIER = 10;\n // The migrator contract. It has a lot of power. Can only be set through governance (owner).\n IMigratorChef public migrator;\n // Info of each pool.\n PoolInfo[] public poolInfo;\n // Info of each user that stakes LP tokens.\n mapping(uint256 => mapping(address => UserInfo)) public userInfo;\n // Total allocation poitns. Must be the sum of all allocation points in all pools.\n uint256 public totalAllocPoint = 0;\n // The block number when SUSHI mining starts.\n uint256 public startBlock;\n event Deposit(address indexed user, uint256 indexed pid, uint256 amount);\n event Withdraw(address indexed user, uint256 indexed pid, uint256 amount);\n event EmergencyWithdraw(\n address indexed user,\n uint256 indexed pid,\n uint256 amount\n );\n\n constructor(\n SushiToken _sushi,\n address _devaddr,\n uint256 _sushiPerBlock,\n uint256 _startBlock,\n uint256 _bonusEndBlock\n ) public {\n sushi = _sushi;\n devaddr = _devaddr;\n sushiPerBlock = _sushiPerBlock;\n bonusEndBlock = _bonusEndBlock;\n startBlock = _startBlock;\n }\n\n function poolLength() external view returns (uint256) {\n return poolInfo.length;\n }\n\n // Add a new lp to the pool. Can only be called by the owner.\n // XXX DO NOT add the same LP token more than once. Rewards will be messed up if you do.\n function add(\n uint256 _allocPoint,\n IERC20 _lpToken,\n bool _withUpdate\n ) public onlyOwner {\n if (_withUpdate) {\n massUpdatePools();\n }\n uint256 lastRewardBlock =\n block.number > startBlock ? block.number : startBlock;\n totalAllocPoint = totalAllocPoint.add(_allocPoint);\n poolInfo.push(\n PoolInfo({\n lpToken: _lpToken,\n allocPoint: _allocPoint,\n lastRewardBlock: lastRewardBlock,\n accSushiPerShare: 0\n })\n );\n }\n\n // Update the given pool's SUSHI allocation point. Can only be called by the owner.\n function set(\n uint256 _pid,\n uint256 _allocPoint,\n bool _withUpdate\n ) public onlyOwner {\n if (_withUpdate) {\n massUpdatePools();\n }\n totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(\n _allocPoint\n );\n poolInfo[_pid].allocPoint = _allocPoint;\n }\n\n // Set the migrator contract. Can only be called by the owner.\n function setMigrator(IMigratorChef _migrator) public onlyOwner {\n migrator = _migrator;\n }\n\n // Migrate lp token to another lp contract. Can be called by anyone. We trust that migrator contract is good.\n function migrate(uint256 _pid) public {\n require(address(migrator) != address(0), \"migrate: no migrator\");\n PoolInfo storage pool = poolInfo[_pid];\n IERC20 lpToken = pool.lpToken;\n uint256 bal = lpToken.balanceOf(address(this));\n lpToken.safeApprove(address(migrator), bal);\n IERC20 newLpToken = migrator.migrate(lpToken);\n require(bal == newLpToken.balanceOf(address(this)), \"migrate: bad\");\n pool.lpToken = newLpToken;\n }\n\n // Return reward multiplier over the given _from to _to block.\n function getMultiplier(uint256 _from, uint256 _to)\n public\n view\n returns (uint256)\n {\n if (_to <= bonusEndBlock) {\n return _to.sub(_from).mul(BONUS_MULTIPLIER);\n } else if (_from >= bonusEndBlock) {\n return _to.sub(_from);\n } else {\n return\n bonusEndBlock.sub(_from).mul(BONUS_MULTIPLIER).add(\n _to.sub(bonusEndBlock)\n );\n }\n }\n\n // View function to see pending SUSHIs on frontend.\n function pendingSushi(uint256 _pid, address _user)\n external\n view\n returns (uint256)\n {\n PoolInfo storage pool = poolInfo[_pid];\n UserInfo storage user = userInfo[_pid][_user];\n uint256 accSushiPerShare = pool.accSushiPerShare;\n uint256 lpSupply = pool.lpToken.balanceOf(address(this));\n if (block.number > pool.lastRewardBlock && lpSupply != 0) {\n uint256 multiplier =\n getMultiplier(pool.lastRewardBlock, block.number);\n uint256 sushiReward =\n multiplier.mul(sushiPerBlock).mul(pool.allocPoint).div(\n totalAllocPoint\n );\n accSushiPerShare = accSushiPerShare.add(\n sushiReward.mul(1e12).div(lpSupply)\n );\n }\n return user.amount.mul(accSushiPerShare).div(1e12).sub(user.rewardDebt);\n }\n\n // Update reward vairables for all pools. Be careful of gas spending!\n function massUpdatePools() public {\n uint256 length = poolInfo.length;\n for (uint256 pid = 0; pid < length; ++pid) {\n updatePool(pid);\n }\n }\n\n // Update reward variables of the given pool to be up-to-date.\n function updatePool(uint256 _pid) public {\n PoolInfo storage pool = poolInfo[_pid];\n if (block.number <= pool.lastRewardBlock) {\n return;\n }\n uint256 lpSupply = pool.lpToken.balanceOf(address(this));\n if (lpSupply == 0) {\n pool.lastRewardBlock = block.number;\n return;\n }\n uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);\n uint256 sushiReward =\n multiplier.mul(sushiPerBlock).mul(pool.allocPoint).div(\n totalAllocPoint\n );\n sushi.mint(devaddr, sushiReward.div(10));\n sushi.mint(address(this), sushiReward);\n pool.accSushiPerShare = pool.accSushiPerShare.add(\n sushiReward.mul(1e12).div(lpSupply)\n );\n pool.lastRewardBlock = block.number;\n }\n\n // Deposit LP tokens to MasterChef for SUSHI allocation.\n function deposit(uint256 _pid, uint256 _amount) public {\n PoolInfo storage pool = poolInfo[_pid];\n UserInfo storage user = userInfo[_pid][msg.sender];\n updatePool(_pid);\n if (user.amount > 0) {\n uint256 pending =\n user.amount.mul(pool.accSushiPerShare).div(1e12).sub(\n user.rewardDebt\n );\n safeSushiTransfer(msg.sender, pending);\n }\n pool.lpToken.safeTransferFrom(\n address(msg.sender),\n address(this),\n _amount\n );\n user.amount = user.amount.add(_amount);\n user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div(1e12);\n emit Deposit(msg.sender, _pid, _amount);\n }\n\n // Withdraw LP tokens from MasterChef.\n function withdraw(uint256 _pid, uint256 _amount) public {\n PoolInfo storage pool = poolInfo[_pid];\n UserInfo storage user = userInfo[_pid][msg.sender];\n require(user.amount >= _amount, \"withdraw: not good\");\n updatePool(_pid);\n uint256 pending =\n user.amount.mul(pool.accSushiPerShare).div(1e12).sub(\n user.rewardDebt\n );\n safeSushiTransfer(msg.sender, pending);\n user.amount = user.amount.sub(_amount);\n user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div(1e12);\n pool.lpToken.safeTransfer(address(msg.sender), _amount);\n emit Withdraw(msg.sender, _pid, _amount);\n }\n\n // Withdraw without caring about rewards. EMERGENCY ONLY.\n function emergencyWithdraw(uint256 _pid) public {\n PoolInfo storage pool = poolInfo[_pid];\n UserInfo storage user = userInfo[_pid][msg.sender];\n pool.lpToken.safeTransfer(address(msg.sender), user.amount);\n emit EmergencyWithdraw(msg.sender, _pid, user.amount);\n user.amount = 0;\n user.rewardDebt = 0;\n }\n\n // Safe sushi transfer function, just in case if rounding error causes pool to not have enough SUSHIs.\n function safeSushiTransfer(address _to, uint256 _amount) internal {\n uint256 sushiBal = sushi.balanceOf(address(this));\n if (_amount > sushiBal) {\n sushi.transfer(_to, sushiBal);\n } else {\n sushi.transfer(_to, _amount);\n }\n }\n\n // Update dev address by the previous dev.\n function dev(address _devaddr) public {\n require(msg.sender == devaddr, \"dev: wut?\");\n devaddr = _devaddr;\n }\n\n // Update bonus end block\n function updateBonusEndBlock(uint256 _bonusEndBlock) public onlyOwner {\n bonusEndBlock = _bonusEndBlock;\n }\n}\n",
+ "contracts/Migrator.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.6.12;\n\nimport \"./uniswapv2/interfaces/IUniswapV2Pair.sol\";\nimport \"./uniswapv2/interfaces/IUniswapV2Factory.sol\";\n\n\ncontract Migrator {\n address public chef;\n address public oldFactory;\n IUniswapV2Factory public factory;\n uint256 public notBeforeBlock;\n uint256 public desiredLiquidity = uint256(-1);\n\n constructor(\n address _chef,\n address _oldFactory,\n IUniswapV2Factory _factory,\n uint256 _notBeforeBlock\n ) public {\n chef = _chef;\n oldFactory = _oldFactory;\n factory = _factory;\n notBeforeBlock = _notBeforeBlock;\n }\n\n function migrate(IUniswapV2Pair orig) public returns (IUniswapV2Pair) {\n require(msg.sender == chef, \"not from master chef\");\n require(block.number >= notBeforeBlock, \"too early to migrate\");\n require(orig.factory() == oldFactory, \"not from old factory\");\n address token0 = orig.token0();\n address token1 = orig.token1();\n IUniswapV2Pair pair = IUniswapV2Pair(factory.getPair(token0, token1));\n if (pair == IUniswapV2Pair(address(0))) {\n pair = IUniswapV2Pair(factory.createPair(token0, token1));\n }\n uint256 lp = orig.balanceOf(msg.sender);\n if (lp == 0) return pair;\n desiredLiquidity = lp;\n orig.transferFrom(msg.sender, address(orig), lp);\n orig.burn(address(pair));\n pair.mint(msg.sender);\n desiredLiquidity = uint256(-1);\n return pair;\n }\n}",
+ "contracts/Ownable.sol": "// SPDX-License-Identifier: MIT\n// Audit on 5-Jan-2021 by Keno and BoringCrypto\n\n// P1 - P3: OK\npragma solidity 0.6.12;\n\n// Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol + Claimable.sol\n// Edited by BoringCrypto\n\n// T1 - T4: OK\ncontract OwnableData {\n // V1 - V5: OK\n address public owner;\n // V1 - V5: OK\n address public pendingOwner;\n}\n\n// T1 - T4: OK\ncontract Ownable is OwnableData {\n // E1: OK\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n constructor () internal {\n owner = msg.sender;\n emit OwnershipTransferred(address(0), msg.sender);\n }\n\n // F1 - F9: OK\n // C1 - C21: OK\n function transferOwnership(address newOwner, bool direct, bool renounce) public onlyOwner {\n if (direct) {\n // Checks\n require(newOwner != address(0) || renounce, \"Ownable: zero address\");\n\n // Effects\n emit OwnershipTransferred(owner, newOwner);\n owner = newOwner;\n } else {\n // Effects\n pendingOwner = newOwner;\n }\n }\n\n // F1 - F9: OK\n // C1 - C21: OK\n function claimOwnership() public {\n address _pendingOwner = pendingOwner;\n\n // Checks\n require(msg.sender == _pendingOwner, \"Ownable: caller != pending owner\");\n\n // Effects\n emit OwnershipTransferred(owner, _pendingOwner);\n owner = _pendingOwner;\n pendingOwner = address(0);\n }\n\n // M1 - M5: OK\n // C1 - C21: OK\n modifier onlyOwner() {\n require(msg.sender == owner, \"Ownable: caller is not the owner\");\n _;\n }\n}\n",
+ "contracts/SushiBar.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.6.12;\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport \"@openzeppelin/contracts/math/SafeMath.sol\";\n\n// SushiBar is the coolest bar in town. You come in with some Sushi, and leave with more! The longer you stay, the more Sushi you get.\n//\n// This contract handles swapping to and from xSushi, SushiSwap's staking token.\ncontract SushiBar is ERC20(\"SushiBar\", \"xSUSHI\"){\n using SafeMath for uint256;\n IERC20 public sushi;\n\n // Define the Sushi token contract\n constructor(IERC20 _sushi) public {\n sushi = _sushi;\n }\n\n // Enter the bar. Pay some SUSHIs. Earn some shares.\n // Locks Sushi and mints xSushi\n function enter(uint256 _amount) public {\n // Gets the amount of Sushi locked in the contract\n uint256 totalSushi = sushi.balanceOf(address(this));\n // Gets the amount of xSushi in existence\n uint256 totalShares = totalSupply();\n // If no xSushi exists, mint it 1:1 to the amount put in\n if (totalShares == 0 || totalSushi == 0) {\n _mint(msg.sender, _amount);\n } \n // Calculate and mint the amount of xSushi the Sushi is worth. The ratio will change overtime, as xSushi is burned/minted and Sushi deposited + gained from fees / withdrawn.\n else {\n uint256 what = _amount.mul(totalShares).div(totalSushi);\n _mint(msg.sender, what);\n }\n // Lock the Sushi in the contract\n sushi.transferFrom(msg.sender, address(this), _amount);\n }\n\n // Leave the bar. Claim back your SUSHIs.\n // Unlocks the staked + gained Sushi and burns xSushi\n function leave(uint256 _share) public {\n // Gets the amount of xSushi in existence\n uint256 totalShares = totalSupply();\n // Calculates the amount of Sushi the xSushi is worth\n uint256 what = _share.mul(sushi.balanceOf(address(this))).div(totalShares);\n _burn(msg.sender, _share);\n sushi.transfer(msg.sender, what);\n }\n}\n",
+ "contracts/SushiMaker.sol": "// SPDX-License-Identifier: MIT\n\n// P1 - P3: OK\npragma solidity 0.6.12;\nimport \"./libraries/SafeMath.sol\";\nimport \"./libraries/SafeERC20.sol\";\n\nimport \"./uniswapv2/interfaces/IUniswapV2ERC20.sol\";\nimport \"./uniswapv2/interfaces/IUniswapV2Pair.sol\";\nimport \"./uniswapv2/interfaces/IUniswapV2Factory.sol\";\n\nimport \"./Ownable.sol\";\n\n// SushiMaker is MasterChef's left hand and kinda a wizard. He can cook up Sushi from pretty much anything!\n// This contract handles \"serving up\" rewards for xSushi holders by trading tokens collected from fees for Sushi.\n\n// T1 - T4: OK\ncontract SushiMaker is Ownable {\n using SafeMath for uint256;\n using SafeERC20 for IERC20;\n\n // V1 - V5: OK\n IUniswapV2Factory public immutable factory;\n //0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac\n // V1 - V5: OK\n address public immutable bar;\n //0x8798249c2E607446EfB7Ad49eC89dD1865Ff4272\n // V1 - V5: OK\n address private immutable sushi;\n //0x6B3595068778DD592e39A122f4f5a5cF09C90fE2\n // V1 - V5: OK\n address private immutable weth;\n //0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\n // V1 - V5: OK\n mapping(address => address) internal _bridges;\n\n // E1: OK\n event LogBridgeSet(address indexed token, address indexed bridge);\n // E1: OK\n event LogConvert(\n address indexed server,\n address indexed token0,\n address indexed token1,\n uint256 amount0,\n uint256 amount1,\n uint256 amountSUSHI\n );\n\n constructor(\n address _factory,\n address _bar,\n address _sushi,\n address _weth\n ) public {\n factory = IUniswapV2Factory(_factory);\n bar = _bar;\n sushi = _sushi;\n weth = _weth;\n }\n\n // F1 - F10: OK\n // C1 - C24: OK\n function bridgeFor(address token) public view returns (address bridge) {\n bridge = _bridges[token];\n if (bridge == address(0)) {\n bridge = weth;\n }\n }\n\n // F1 - F10: OK\n // C1 - C24: OK\n function setBridge(address token, address bridge) external onlyOwner {\n // Checks\n require(\n token != sushi && token != weth && token != bridge,\n \"SushiMaker: Invalid bridge\"\n );\n\n // Effects\n _bridges[token] = bridge;\n emit LogBridgeSet(token, bridge);\n }\n\n // M1 - M5: OK\n // C1 - C24: OK\n // C6: It's not a fool proof solution, but it prevents flash loans, so here it's ok to use tx.origin\n modifier onlyEOA() {\n // Try to make flash-loan exploit harder to do by only allowing externally owned addresses.\n require(msg.sender == tx.origin, \"SushiMaker: must use EOA\");\n _;\n }\n\n // F1 - F10: OK\n // F3: _convert is separate to save gas by only checking the 'onlyEOA' modifier once in case of convertMultiple\n // F6: There is an exploit to add lots of SUSHI to the bar, run convert, then remove the SUSHI again.\n // As the size of the SushiBar has grown, this requires large amounts of funds and isn't super profitable anymore\n // The onlyEOA modifier prevents this being done with a flash loan.\n // C1 - C24: OK\n function convert(address token0, address token1) external onlyEOA() {\n _convert(token0, token1);\n }\n\n // F1 - F10: OK, see convert\n // C1 - C24: OK\n // C3: Loop is under control of the caller\n function convertMultiple(\n address[] calldata token0,\n address[] calldata token1\n ) external onlyEOA() {\n // TODO: This can be optimized a fair bit, but this is safer and simpler for now\n uint256 len = token0.length;\n for (uint256 i = 0; i < len; i++) {\n _convert(token0[i], token1[i]);\n }\n }\n\n // F1 - F10: OK\n // C1- C24: OK\n function _convert(address token0, address token1) internal {\n // Interactions\n // S1 - S4: OK\n IUniswapV2Pair pair = IUniswapV2Pair(factory.getPair(token0, token1));\n require(address(pair) != address(0), \"SushiMaker: Invalid pair\");\n // balanceOf: S1 - S4: OK\n // transfer: X1 - X5: OK\n IERC20(address(pair)).safeTransfer(\n address(pair),\n pair.balanceOf(address(this))\n );\n // X1 - X5: OK\n (uint256 amount0, uint256 amount1) = pair.burn(address(this));\n if (token0 != pair.token0()) {\n (amount0, amount1) = (amount1, amount0);\n }\n emit LogConvert(\n msg.sender,\n token0,\n token1,\n amount0,\n amount1,\n _convertStep(token0, token1, amount0, amount1)\n );\n }\n\n // F1 - F10: OK\n // C1 - C24: OK\n // All safeTransfer, _swap, _toSUSHI, _convertStep: X1 - X5: OK\n function _convertStep(\n address token0,\n address token1,\n uint256 amount0,\n uint256 amount1\n ) internal returns (uint256 sushiOut) {\n // Interactions\n if (token0 == token1) {\n uint256 amount = amount0.add(amount1);\n if (token0 == sushi) {\n IERC20(sushi).safeTransfer(bar, amount);\n sushiOut = amount;\n } else if (token0 == weth) {\n sushiOut = _toSUSHI(weth, amount);\n } else {\n address bridge = bridgeFor(token0);\n amount = _swap(token0, bridge, amount, address(this));\n sushiOut = _convertStep(bridge, bridge, amount, 0);\n }\n } else if (token0 == sushi) {\n // eg. SUSHI - ETH\n IERC20(sushi).safeTransfer(bar, amount0);\n sushiOut = _toSUSHI(token1, amount1).add(amount0);\n } else if (token1 == sushi) {\n // eg. USDT - SUSHI\n IERC20(sushi).safeTransfer(bar, amount1);\n sushiOut = _toSUSHI(token0, amount0).add(amount1);\n } else if (token0 == weth) {\n // eg. ETH - USDC\n sushiOut = _toSUSHI(\n weth,\n _swap(token1, weth, amount1, address(this)).add(amount0)\n );\n } else if (token1 == weth) {\n // eg. USDT - ETH\n sushiOut = _toSUSHI(\n weth,\n _swap(token0, weth, amount0, address(this)).add(amount1)\n );\n } else {\n // eg. MIC - USDT\n address bridge0 = bridgeFor(token0);\n address bridge1 = bridgeFor(token1);\n if (bridge0 == token1) {\n // eg. MIC - USDT - and bridgeFor(MIC) = USDT\n sushiOut = _convertStep(\n bridge0,\n token1,\n _swap(token0, bridge0, amount0, address(this)),\n amount1\n );\n } else if (bridge1 == token0) {\n // eg. WBTC - DSD - and bridgeFor(DSD) = WBTC\n sushiOut = _convertStep(\n token0,\n bridge1,\n amount0,\n _swap(token1, bridge1, amount1, address(this))\n );\n } else {\n sushiOut = _convertStep(\n bridge0,\n bridge1, // eg. USDT - DSD - and bridgeFor(DSD) = WBTC\n _swap(token0, bridge0, amount0, address(this)),\n _swap(token1, bridge1, amount1, address(this))\n );\n }\n }\n }\n\n // F1 - F10: OK\n // C1 - C24: OK\n // All safeTransfer, swap: X1 - X5: OK\n function _swap(\n address fromToken,\n address toToken,\n uint256 amountIn,\n address to\n ) internal returns (uint256 amountOut) {\n // Checks\n // X1 - X5: OK\n IUniswapV2Pair pair =\n IUniswapV2Pair(factory.getPair(fromToken, toToken));\n require(address(pair) != address(0), \"SushiMaker: Cannot convert\");\n\n // Interactions\n // X1 - X5: OK\n (uint256 reserve0, uint256 reserve1, ) = pair.getReserves();\n uint256 amountInWithFee = amountIn.mul(997);\n if (fromToken == pair.token0()) {\n amountOut =\n amountInWithFee.mul(reserve1) /\n reserve0.mul(1000).add(amountInWithFee);\n IERC20(fromToken).safeTransfer(address(pair), amountIn);\n pair.swap(0, amountOut, to, new bytes(0));\n // TODO: Add maximum slippage?\n } else {\n amountOut =\n amountInWithFee.mul(reserve0) /\n reserve1.mul(1000).add(amountInWithFee);\n IERC20(fromToken).safeTransfer(address(pair), amountIn);\n pair.swap(amountOut, 0, to, new bytes(0));\n // TODO: Add maximum slippage?\n }\n }\n\n // F1 - F10: OK\n // C1 - C24: OK\n function _toSUSHI(address token, uint256 amountIn)\n internal\n returns (uint256 amountOut)\n {\n // X1 - X5: OK\n amountOut = _swap(token, sushi, amountIn, bar);\n }\n}\n",
+ "contracts/SushiMakerKashi.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.6.12;\nimport \"./libraries/SafeMath.sol\";\nimport \"./libraries/SafeERC20.sol\";\n\nimport \"./uniswapv2/interfaces/IUniswapV2Pair.sol\";\nimport \"./uniswapv2/interfaces/IUniswapV2Factory.sol\";\n\nimport \"./Ownable.sol\";\n\ninterface IBentoBoxWithdraw {\n function withdraw(\n IERC20 token_,\n address from,\n address to,\n uint256 amount,\n uint256 share\n ) external returns (uint256 amountOut, uint256 shareOut);\n}\n\ninterface IKashiWithdrawFee {\n function asset() external view returns (address);\n function balanceOf(address account) external view returns (uint256);\n function withdrawFees() external;\n function removeAsset(address to, uint256 fraction) external returns (uint256 share);\n}\n\n// SushiMakerKashi is MasterChef's left hand and kinda a wizard. He can cook up Sushi from pretty much anything!\n// This contract handles \"serving up\" rewards for xSushi holders by trading tokens collected from Kashi fees for Sushi.\ncontract SushiMakerKashi is Ownable {\n using SafeMath for uint256;\n using SafeERC20 for IERC20;\n\n IUniswapV2Factory private immutable factory;\n //0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac\n address private immutable bar;\n //0x8798249c2E607446EfB7Ad49eC89dD1865Ff4272\n IBentoBoxWithdraw private immutable bentoBox;\n //0xF5BCE5077908a1b7370B9ae04AdC565EBd643966 \n address private immutable sushi;\n //0x6B3595068778DD592e39A122f4f5a5cF09C90fE2\n address private immutable weth;\n //0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n bytes32 private immutable pairCodeHash;\n //0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303\n\n mapping(address => address) private _bridges;\n\n event LogBridgeSet(address indexed token, address indexed bridge);\n event LogConvert(\n address indexed server,\n address indexed token0,\n uint256 amount0,\n uint256 amountBENTO,\n uint256 amountSUSHI\n );\n\n constructor(\n IUniswapV2Factory _factory,\n address _bar,\n IBentoBoxWithdraw _bentoBox,\n address _sushi,\n address _weth,\n bytes32 _pairCodeHash\n ) public {\n factory = _factory;\n bar = _bar;\n bentoBox = _bentoBox;\n sushi = _sushi;\n weth = _weth;\n pairCodeHash = _pairCodeHash;\n }\n\n function setBridge(address token, address bridge) external onlyOwner {\n // Checks\n require(\n token != sushi && token != weth && token != bridge,\n \"Maker: Invalid bridge\"\n );\n // Effects\n _bridges[token] = bridge;\n emit LogBridgeSet(token, bridge);\n }\n\n modifier onlyEOA() {\n // Try to make flash-loan exploit harder to do by only allowing externally-owned addresses.\n require(msg.sender == tx.origin, \"Maker: Must use EOA\");\n _;\n }\n\n function convert(IKashiWithdrawFee kashiPair) external onlyEOA {\n _convert(kashiPair);\n }\n\n function convertMultiple(IKashiWithdrawFee[] calldata kashiPair) external onlyEOA {\n for (uint256 i = 0; i < kashiPair.length; i++) {\n _convert(kashiPair[i]);\n }\n }\n\n function _convert(IKashiWithdrawFee kashiPair) private {\n // update Kashi fees for this Maker contract (`feeTo`)\n kashiPair.withdrawFees();\n\n // convert updated Kashi balance to Bento shares\n uint256 bentoShares = kashiPair.removeAsset(address(this), kashiPair.balanceOf(address(this)));\n\n // convert Bento shares to underlying Kashi asset (`token0`) balance (`amount0`) for Maker\n address token0 = kashiPair.asset();\n (uint256 amount0, ) = bentoBox.withdraw(IERC20(token0), address(this), address(this), 0, bentoShares);\n\n emit LogConvert(\n msg.sender,\n token0,\n amount0,\n bentoShares,\n _convertStep(token0, amount0)\n );\n }\n\n function _convertStep(address token0, uint256 amount0) private returns (uint256 sushiOut) {\n if (token0 == sushi) {\n IERC20(token0).safeTransfer(bar, amount0);\n sushiOut = amount0;\n } else if (token0 == weth) {\n sushiOut = _swap(token0, sushi, amount0, bar);\n } else {\n address bridge = _bridges[token0];\n if (bridge == address(0)) {\n bridge = weth;\n }\n uint256 amountOut = _swap(token0, bridge, amount0, address(this));\n sushiOut = _convertStep(bridge, amountOut);\n }\n }\n\n function _swap(\n address fromToken,\n address toToken,\n uint256 amountIn,\n address to\n ) private returns (uint256 amountOut) {\n (address token0, address token1) = fromToken < toToken ? (fromToken, toToken) : (toToken, fromToken);\n IUniswapV2Pair pair =\n IUniswapV2Pair(\n uint256(\n keccak256(abi.encodePacked(hex\"ff\", factory, keccak256(abi.encodePacked(token0, token1)), pairCodeHash))\n )\n );\n \n (uint256 reserve0, uint256 reserve1, ) = pair.getReserves();\n uint256 amountInWithFee = amountIn.mul(997);\n \n if (toToken > fromToken) {\n amountOut =\n amountInWithFee.mul(reserve1) /\n reserve0.mul(1000).add(amountInWithFee);\n IERC20(fromToken).safeTransfer(address(pair), amountIn);\n pair.swap(0, amountOut, to, \"\");\n } else {\n amountOut =\n amountInWithFee.mul(reserve0) /\n reserve1.mul(1000).add(amountInWithFee);\n IERC20(fromToken).safeTransfer(address(pair), amountIn);\n pair.swap(amountOut, 0, to, \"\");\n }\n }\n}\n",
+ "contracts/SushiRoll.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.6.12;\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/SafeERC20.sol\";\nimport \"./uniswapv2/interfaces/IUniswapV2Pair.sol\";\nimport \"./uniswapv2/interfaces/IUniswapV2Router01.sol\";\nimport \"./uniswapv2/interfaces/IUniswapV2Factory.sol\";\nimport \"./uniswapv2/libraries/UniswapV2Library.sol\";\n\n// SushiRoll helps your migrate your existing Uniswap LP tokens to SushiSwap LP ones\ncontract SushiRoll {\n using SafeERC20 for IERC20;\n\n IUniswapV2Router01 public oldRouter;\n IUniswapV2Router01 public router;\n\n constructor(IUniswapV2Router01 _oldRouter, IUniswapV2Router01 _router) public {\n oldRouter = _oldRouter;\n router = _router;\n }\n\n function migrateWithPermit(\n address tokenA,\n address tokenB,\n uint256 liquidity,\n uint256 amountAMin,\n uint256 amountBMin,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) public {\n IUniswapV2Pair pair = IUniswapV2Pair(pairForOldRouter(tokenA, tokenB));\n pair.permit(msg.sender, address(this), liquidity, deadline, v, r, s);\n\n migrate(tokenA, tokenB, liquidity, amountAMin, amountBMin, deadline);\n }\n\n // msg.sender should have approved 'liquidity' amount of LP token of 'tokenA' and 'tokenB'\n function migrate(\n address tokenA,\n address tokenB,\n uint256 liquidity,\n uint256 amountAMin,\n uint256 amountBMin,\n uint256 deadline\n ) public {\n require(deadline >= block.timestamp, 'SushiSwap: EXPIRED');\n\n // Remove liquidity from the old router with permit\n (uint256 amountA, uint256 amountB) = removeLiquidity(\n tokenA,\n tokenB,\n liquidity,\n amountAMin,\n amountBMin,\n deadline\n );\n\n // Add liquidity to the new router\n (uint256 pooledAmountA, uint256 pooledAmountB) = addLiquidity(tokenA, tokenB, amountA, amountB);\n\n // Send remaining tokens to msg.sender\n if (amountA > pooledAmountA) {\n IERC20(tokenA).safeTransfer(msg.sender, amountA - pooledAmountA);\n }\n if (amountB > pooledAmountB) {\n IERC20(tokenB).safeTransfer(msg.sender, amountB - pooledAmountB);\n }\n }\n\n function removeLiquidity(\n address tokenA,\n address tokenB,\n uint256 liquidity,\n uint256 amountAMin,\n uint256 amountBMin,\n uint256 deadline\n ) internal returns (uint256 amountA, uint256 amountB) {\n IUniswapV2Pair pair = IUniswapV2Pair(pairForOldRouter(tokenA, tokenB));\n pair.transferFrom(msg.sender, address(pair), liquidity);\n (uint256 amount0, uint256 amount1) = pair.burn(address(this));\n (address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);\n (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);\n require(amountA >= amountAMin, 'SushiRoll: INSUFFICIENT_A_AMOUNT');\n require(amountB >= amountBMin, 'SushiRoll: INSUFFICIENT_B_AMOUNT');\n }\n\n // calculates the CREATE2 address for a pair without making any external calls\n function pairForOldRouter(address tokenA, address tokenB) internal view returns (address pair) {\n (address token0, address token1) = UniswapV2Library.sortTokens(tokenA, tokenB);\n pair = address(uint(keccak256(abi.encodePacked(\n hex'ff',\n oldRouter.factory(),\n keccak256(abi.encodePacked(token0, token1)),\n hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash\n ))));\n }\n\n function addLiquidity(\n address tokenA,\n address tokenB,\n uint256 amountADesired,\n uint256 amountBDesired\n ) internal returns (uint amountA, uint amountB) {\n (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired);\n address pair = UniswapV2Library.pairFor(router.factory(), tokenA, tokenB);\n IERC20(tokenA).safeTransfer(pair, amountA);\n IERC20(tokenB).safeTransfer(pair, amountB);\n IUniswapV2Pair(pair).mint(msg.sender);\n }\n\n function _addLiquidity(\n address tokenA,\n address tokenB,\n uint256 amountADesired,\n uint256 amountBDesired\n ) internal returns (uint256 amountA, uint256 amountB) {\n // create the pair if it doesn't exist yet\n IUniswapV2Factory factory = IUniswapV2Factory(router.factory());\n if (factory.getPair(tokenA, tokenB) == address(0)) {\n factory.createPair(tokenA, tokenB);\n }\n (uint256 reserveA, uint256 reserveB) = UniswapV2Library.getReserves(address(factory), tokenA, tokenB);\n if (reserveA == 0 && reserveB == 0) {\n (amountA, amountB) = (amountADesired, amountBDesired);\n } else {\n uint256 amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);\n if (amountBOptimal <= amountBDesired) {\n (amountA, amountB) = (amountADesired, amountBOptimal);\n } else {\n uint256 amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);\n assert(amountAOptimal <= amountADesired);\n (amountA, amountB) = (amountAOptimal, amountBDesired);\n }\n }\n }\n}\n",
+ "contracts/SushiToken.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.6.12;\n\nimport \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\n// WARNING: There is a known vuln contained within this contract related to vote delegation, \n// it's NOT recommmended to use this in production. \n\n// SushiToken with Governance.\ncontract SushiToken is ERC20(\"Powswap\", \"POW\"), Ownable {\n /// @notice Creates `_amount` token to `_to`. Must only be called by the owner (MasterChef).\n function mint(address _to, uint256 _amount) public onlyOwner {\n _mint(_to, _amount);\n _moveDelegates(address(0), _delegates[_to], _amount);\n }\n\n // Copied and modified from YAM code:\n // https://github.com/yam-finance/yam-protocol/blob/master/contracts/token/YAMGovernanceStorage.sol\n // https://github.com/yam-finance/yam-protocol/blob/master/contracts/token/YAMGovernance.sol\n // Which is copied and modified from COMPOUND:\n // https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/Comp.sol\n\n /// @notice A record of each accounts delegate\n mapping (address => address) internal _delegates;\n\n /// @notice A checkpoint for marking number of votes from a given block\n struct Checkpoint {\n uint32 fromBlock;\n uint256 votes;\n }\n\n /// @notice A record of votes checkpoints for each account, by index\n mapping (address => mapping (uint32 => Checkpoint)) public checkpoints;\n\n /// @notice The number of checkpoints for each account\n mapping (address => uint32) public numCheckpoints;\n\n /// @notice The EIP-712 typehash for the contract's domain\n bytes32 public constant DOMAIN_TYPEHASH = keccak256(\"EIP712Domain(string name,uint256 chainId,address verifyingContract)\");\n\n /// @notice The EIP-712 typehash for the delegation struct used by the contract\n bytes32 public constant DELEGATION_TYPEHASH = keccak256(\"Delegation(address delegatee,uint256 nonce,uint256 expiry)\");\n\n /// @notice A record of states for signing / validating signatures\n mapping (address => uint) public nonces;\n\n /// @notice An event thats emitted when an account changes its delegate\n event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);\n\n /// @notice An event thats emitted when a delegate account's vote balance changes\n event DelegateVotesChanged(address indexed delegate, uint previousBalance, uint newBalance);\n\n /**\n * @notice Delegate votes from `msg.sender` to `delegatee`\n * @param delegator The address to get delegatee for\n */\n function delegates(address delegator)\n external\n view\n returns (address)\n {\n return _delegates[delegator];\n }\n\n /**\n * @notice Delegate votes from `msg.sender` to `delegatee`\n * @param delegatee The address to delegate votes to\n */\n function delegate(address delegatee) external {\n return _delegate(msg.sender, delegatee);\n }\n\n /**\n * @notice Delegates votes from signatory to `delegatee`\n * @param delegatee The address to delegate votes to\n * @param nonce The contract state required to match the signature\n * @param expiry The time at which to expire the signature\n * @param v The recovery byte of the signature\n * @param r Half of the ECDSA signature pair\n * @param s Half of the ECDSA signature pair\n */\n function delegateBySig(\n address delegatee,\n uint nonce,\n uint expiry,\n uint8 v,\n bytes32 r,\n bytes32 s\n )\n external\n {\n bytes32 domainSeparator = keccak256(\n abi.encode(\n DOMAIN_TYPEHASH,\n keccak256(bytes(name())),\n getChainId(),\n address(this)\n )\n );\n\n bytes32 structHash = keccak256(\n abi.encode(\n DELEGATION_TYPEHASH,\n delegatee,\n nonce,\n expiry\n )\n );\n\n bytes32 digest = keccak256(\n abi.encodePacked(\n \"\\x19\\x01\",\n domainSeparator,\n structHash\n )\n );\n\n address signatory = ecrecover(digest, v, r, s);\n require(signatory != address(0), \"SUSHI::delegateBySig: invalid signature\");\n require(nonce == nonces[signatory]++, \"SUSHI::delegateBySig: invalid nonce\");\n require(now <= expiry, \"SUSHI::delegateBySig: signature expired\");\n return _delegate(signatory, delegatee);\n }\n\n /**\n * @notice Gets the current votes balance for `account`\n * @param account The address to get votes balance\n * @return The number of current votes for `account`\n */\n function getCurrentVotes(address account)\n external\n view\n returns (uint256)\n {\n uint32 nCheckpoints = numCheckpoints[account];\n return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0;\n }\n\n /**\n * @notice Determine the prior number of votes for an account as of a block number\n * @dev Block number must be a finalized block or else this function will revert to prevent misinformation.\n * @param account The address of the account to check\n * @param blockNumber The block number to get the vote balance at\n * @return The number of votes the account had as of the given block\n */\n function getPriorVotes(address account, uint blockNumber)\n external\n view\n returns (uint256)\n {\n require(blockNumber < block.number, \"SUSHI::getPriorVotes: not yet determined\");\n\n uint32 nCheckpoints = numCheckpoints[account];\n if (nCheckpoints == 0) {\n return 0;\n }\n\n // First check most recent balance\n if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) {\n return checkpoints[account][nCheckpoints - 1].votes;\n }\n\n // Next check implicit zero balance\n if (checkpoints[account][0].fromBlock > blockNumber) {\n return 0;\n }\n\n uint32 lower = 0;\n uint32 upper = nCheckpoints - 1;\n while (upper > lower) {\n uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow\n Checkpoint memory cp = checkpoints[account][center];\n if (cp.fromBlock == blockNumber) {\n return cp.votes;\n } else if (cp.fromBlock < blockNumber) {\n lower = center;\n } else {\n upper = center - 1;\n }\n }\n return checkpoints[account][lower].votes;\n }\n\n function _delegate(address delegator, address delegatee)\n internal\n {\n address currentDelegate = _delegates[delegator];\n uint256 delegatorBalance = balanceOf(delegator); // balance of underlying SUSHIs (not scaled);\n _delegates[delegator] = delegatee;\n\n emit DelegateChanged(delegator, currentDelegate, delegatee);\n\n _moveDelegates(currentDelegate, delegatee, delegatorBalance);\n }\n\n function _moveDelegates(address srcRep, address dstRep, uint256 amount) internal {\n if (srcRep != dstRep && amount > 0) {\n if (srcRep != address(0)) {\n // decrease old representative\n uint32 srcRepNum = numCheckpoints[srcRep];\n uint256 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0;\n uint256 srcRepNew = srcRepOld.sub(amount);\n _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew);\n }\n\n if (dstRep != address(0)) {\n // increase new representative\n uint32 dstRepNum = numCheckpoints[dstRep];\n uint256 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0;\n uint256 dstRepNew = dstRepOld.add(amount);\n _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);\n }\n }\n }\n\n function _writeCheckpoint(\n address delegatee,\n uint32 nCheckpoints,\n uint256 oldVotes,\n uint256 newVotes\n )\n internal\n {\n uint32 blockNumber = safe32(block.number, \"SUSHI::_writeCheckpoint: block number exceeds 32 bits\");\n\n if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) {\n checkpoints[delegatee][nCheckpoints - 1].votes = newVotes;\n } else {\n checkpoints[delegatee][nCheckpoints] = Checkpoint(blockNumber, newVotes);\n numCheckpoints[delegatee] = nCheckpoints + 1;\n }\n\n emit DelegateVotesChanged(delegatee, oldVotes, newVotes);\n }\n\n function safe32(uint n, string memory errorMessage) internal pure returns (uint32) {\n require(n < 2**32, errorMessage);\n return uint32(n);\n }\n\n function getChainId() internal pure returns (uint) {\n uint256 chainId;\n assembly { chainId := chainid() }\n return chainId;\n }\n}",
+ "contracts/governance/Timelock.sol": "// SPDX-License-Identifier: MIT\n\n// COPIED FROM https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/GovernorAlpha.sol\n// Copyright 2020 Compound Labs, Inc.\n// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n// Ctrl+f for XXX to see all the modifications.\n\n// XXX: pragma solidity ^0.5.16;\npragma solidity 0.6.12;\n\n// XXX: import \"./SafeMath.sol\";\nimport \"@openzeppelin/contracts/math/SafeMath.sol\";\n\ncontract Timelock {\n using SafeMath for uint;\n\n event NewAdmin(address indexed newAdmin);\n event NewPendingAdmin(address indexed newPendingAdmin);\n event NewDelay(uint indexed newDelay);\n event CancelTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta);\n event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta);\n event QueueTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta);\n\n uint public constant GRACE_PERIOD = 14 days;\n uint public constant MINIMUM_DELAY = 2 days;\n uint public constant MAXIMUM_DELAY = 30 days;\n\n address public admin;\n address public pendingAdmin;\n uint public delay;\n bool public admin_initialized;\n\n mapping (bytes32 => bool) public queuedTransactions;\n\n\n constructor(address admin_, uint delay_) public {\n require(delay_ >= MINIMUM_DELAY, \"Timelock::constructor: Delay must exceed minimum delay.\");\n require(delay_ <= MAXIMUM_DELAY, \"Timelock::constructor: Delay must not exceed maximum delay.\");\n\n admin = admin_;\n delay = delay_;\n admin_initialized = false;\n }\n\n // XXX: function() external payable { }\n receive() external payable { }\n\n function setDelay(uint delay_) public {\n require(msg.sender == address(this), \"Timelock::setDelay: Call must come from Timelock.\");\n require(delay_ >= MINIMUM_DELAY, \"Timelock::setDelay: Delay must exceed minimum delay.\");\n require(delay_ <= MAXIMUM_DELAY, \"Timelock::setDelay: Delay must not exceed maximum delay.\");\n delay = delay_;\n\n emit NewDelay(delay);\n }\n\n function acceptAdmin() public {\n require(msg.sender == pendingAdmin, \"Timelock::acceptAdmin: Call must come from pendingAdmin.\");\n admin = msg.sender;\n pendingAdmin = address(0);\n\n emit NewAdmin(admin);\n }\n\n function setPendingAdmin(address pendingAdmin_) public {\n // allows one time setting of admin for deployment purposes\n if (admin_initialized) {\n require(msg.sender == address(this), \"Timelock::setPendingAdmin: Call must come from Timelock.\");\n } else {\n require(msg.sender == admin, \"Timelock::setPendingAdmin: First call must come from admin.\");\n admin_initialized = true;\n }\n pendingAdmin = pendingAdmin_;\n\n emit NewPendingAdmin(pendingAdmin);\n }\n\n function queueTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public returns (bytes32) {\n require(msg.sender == admin, \"Timelock::queueTransaction: Call must come from admin.\");\n require(eta >= getBlockTimestamp().add(delay), \"Timelock::queueTransaction: Estimated execution block must satisfy delay.\");\n\n bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));\n queuedTransactions[txHash] = true;\n\n emit QueueTransaction(txHash, target, value, signature, data, eta);\n return txHash;\n }\n\n function cancelTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public {\n require(msg.sender == admin, \"Timelock::cancelTransaction: Call must come from admin.\");\n\n bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));\n queuedTransactions[txHash] = false;\n\n emit CancelTransaction(txHash, target, value, signature, data, eta);\n }\n\n function executeTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public payable returns (bytes memory) {\n require(msg.sender == admin, \"Timelock::executeTransaction: Call must come from admin.\");\n\n bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));\n require(queuedTransactions[txHash], \"Timelock::executeTransaction: Transaction hasn't been queued.\");\n require(getBlockTimestamp() >= eta, \"Timelock::executeTransaction: Transaction hasn't surpassed time lock.\");\n require(getBlockTimestamp() <= eta.add(GRACE_PERIOD), \"Timelock::executeTransaction: Transaction is stale.\");\n\n queuedTransactions[txHash] = false;\n\n bytes memory callData;\n\n if (bytes(signature).length == 0) {\n callData = data;\n } else {\n callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);\n }\n\n // solium-disable-next-line security/no-call-value\n (bool success, bytes memory returnData) = target.call.value(value)(callData);\n require(success, \"Timelock::executeTransaction: Transaction execution reverted.\");\n\n emit ExecuteTransaction(txHash, target, value, signature, data, eta);\n\n return returnData;\n }\n\n function getBlockTimestamp() internal view returns (uint) {\n // solium-disable-next-line security/no-block-members\n return block.timestamp;\n }\n}",
+ "contracts/interfaces/IERC20.sol": "// SPDX-License-Identifier: MIT\npragma solidity 0.6.12;\n\ninterface IERC20 {\n function totalSupply() external view returns (uint256);\n function balanceOf(address account) external view returns (uint256);\n function allowance(address owner, address spender) external view returns (uint256);\n function approve(address spender, uint256 amount) external returns (bool);\n event Transfer(address indexed from, address indexed to, uint256 value);\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n // EIP 2612\n function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;\n}",
+ "contracts/libraries/SafeERC20.sol": "// SPDX-License-Identifier: MIT\npragma solidity 0.6.12;\n\nimport \"../interfaces/IERC20.sol\";\n\nlibrary SafeERC20 {\n function safeSymbol(IERC20 token) internal view returns(string memory) {\n (bool success, bytes memory data) = address(token).staticcall(abi.encodeWithSelector(0x95d89b41));\n return success && data.length > 0 ? abi.decode(data, (string)) : \"???\";\n }\n\n function safeName(IERC20 token) internal view returns(string memory) {\n (bool success, bytes memory data) = address(token).staticcall(abi.encodeWithSelector(0x06fdde03));\n return success && data.length > 0 ? abi.decode(data, (string)) : \"???\";\n }\n\n function safeDecimals(IERC20 token) public view returns (uint8) {\n (bool success, bytes memory data) = address(token).staticcall(abi.encodeWithSelector(0x313ce567));\n return success && data.length == 32 ? abi.decode(data, (uint8)) : 18;\n }\n\n function safeTransfer(IERC20 token, address to, uint256 amount) internal {\n (bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(0xa9059cbb, to, amount));\n require(success && (data.length == 0 || abi.decode(data, (bool))), \"SafeERC20: Transfer failed\");\n }\n\n function safeTransferFrom(IERC20 token, address from, uint256 amount) internal {\n (bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(0x23b872dd, from, address(this), amount));\n require(success && (data.length == 0 || abi.decode(data, (bool))), \"SafeERC20: TransferFrom failed\");\n }\n}\n",
+ "contracts/libraries/SafeMath.sol": "// SPDX-License-Identifier: MIT\npragma solidity 0.6.12;\n// a library for performing overflow-safe math, updated with awesomeness from of DappHub (https://github.com/dapphub/ds-math)\nlibrary SafeMath {\n function add(uint256 a, uint256 b) internal pure returns (uint256 c) {require((c = a + b) >= b, \"SafeMath: Add Overflow\");}\n function sub(uint256 a, uint256 b) internal pure returns (uint256 c) {require((c = a - b) <= a, \"SafeMath: Underflow\");}\n function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {require(b == 0 || (c = a * b)/b == a, \"SafeMath: Mul Overflow\");}\n function to128(uint256 a) internal pure returns (uint128 c) {\n require(a <= uint128(-1), \"SafeMath: uint128 Overflow\");\n c = uint128(a);\n }\n}\n\nlibrary SafeMath128 {\n function add(uint128 a, uint128 b) internal pure returns (uint128 c) {require((c = a + b) >= b, \"SafeMath: Add Overflow\");}\n function sub(uint128 a, uint128 b) internal pure returns (uint128 c) {require((c = a - b) <= a, \"SafeMath: Underflow\");}\n}\n",
+ "contracts/mocks/ERC20Mock.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.6.12;\n\nimport \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\ncontract ERC20Mock is ERC20 {\n constructor(\n string memory name,\n string memory symbol,\n uint256 supply\n ) public ERC20(name, symbol) {\n _mint(msg.sender, supply);\n }\n}",
+ "contracts/mocks/SushiMakerExploitMock.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.6.12;\n\nimport \"../SushiMaker.sol\";\n\ncontract SushiMakerExploitMock {\n SushiMaker public immutable sushiMaker;\n constructor (address _sushiMaker) public{\n sushiMaker = SushiMaker(_sushiMaker);\n } \n function convert(address token0, address token1) external {\n sushiMaker.convert(token0, token1);\n }\n}",
+ "contracts/mocks/SushiMakerKashiExploitMock.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.6.12;\n\nimport \"../SushiMakerKashi.sol\";\n\ncontract SushiMakerKashiExploitMock {\n SushiMakerKashi public immutable sushiMaker;\n \n constructor(address _sushiMaker) public {\n sushiMaker = SushiMakerKashi(_sushiMaker);\n } \n \n function convert(IKashiWithdrawFee kashiPair) external {\n sushiMaker.convert(kashiPair);\n }\n}\n",
+ "contracts/mocks/SushiSwapFactoryMock.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.6.12;\n\nimport \"../uniswapv2/UniswapV2Factory.sol\";\n\ncontract SushiSwapFactoryMock is UniswapV2Factory {\n constructor(address _feeToSetter) public UniswapV2Factory(_feeToSetter) {}\n}",
+ "contracts/mocks/SushiSwapPairMock.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.6.12;\n\nimport \"../uniswapv2/UniswapV2Pair.sol\";\n\ncontract SushiSwapPairMock is UniswapV2Pair {\n constructor() public UniswapV2Pair() {}\n}",
+ "contracts/uniswapv2/UniswapV2ERC20.sol": "// SPDX-License-Identifier: GPL-3.0\n\npragma solidity =0.6.12;\n\nimport './libraries/SafeMath.sol';\n\ncontract UniswapV2ERC20 {\n using SafeMathUniswap for uint;\n\n string public constant name = 'SushiSwap LP Token';\n string public constant symbol = 'SLP';\n uint8 public constant decimals = 18;\n uint public totalSupply;\n mapping(address => uint) public balanceOf;\n mapping(address => mapping(address => uint)) public allowance;\n\n bytes32 public DOMAIN_SEPARATOR;\n // keccak256(\"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\");\n bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;\n mapping(address => uint) public nonces;\n\n event Approval(address indexed owner, address indexed spender, uint value);\n event Transfer(address indexed from, address indexed to, uint value);\n\n constructor() public {\n uint chainId;\n assembly {\n chainId := chainid()\n }\n DOMAIN_SEPARATOR = keccak256(\n abi.encode(\n keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),\n keccak256(bytes(name)),\n keccak256(bytes('1')),\n chainId,\n address(this)\n )\n );\n }\n\n function _mint(address to, uint value) internal {\n totalSupply = totalSupply.add(value);\n balanceOf[to] = balanceOf[to].add(value);\n emit Transfer(address(0), to, value);\n }\n\n function _burn(address from, uint value) internal {\n balanceOf[from] = balanceOf[from].sub(value);\n totalSupply = totalSupply.sub(value);\n emit Transfer(from, address(0), value);\n }\n\n function _approve(address owner, address spender, uint value) private {\n allowance[owner][spender] = value;\n emit Approval(owner, spender, value);\n }\n\n function _transfer(address from, address to, uint value) private {\n balanceOf[from] = balanceOf[from].sub(value);\n balanceOf[to] = balanceOf[to].add(value);\n emit Transfer(from, to, value);\n }\n\n function approve(address spender, uint value) external returns (bool) {\n _approve(msg.sender, spender, value);\n return true;\n }\n\n function transfer(address to, uint value) external returns (bool) {\n _transfer(msg.sender, to, value);\n return true;\n }\n\n function transferFrom(address from, address to, uint value) external returns (bool) {\n if (allowance[from][msg.sender] != uint(-1)) {\n allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);\n }\n _transfer(from, to, value);\n return true;\n }\n\n function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {\n require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');\n bytes32 digest = keccak256(\n abi.encodePacked(\n '\\x19\\x01',\n DOMAIN_SEPARATOR,\n keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))\n )\n );\n address recoveredAddress = ecrecover(digest, v, r, s);\n require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');\n _approve(owner, spender, value);\n }\n}\n",
+ "contracts/uniswapv2/UniswapV2Factory.sol": "// SPDX-License-Identifier: GPL-3.0\n\npragma solidity =0.6.12;\n\nimport './interfaces/IUniswapV2Factory.sol';\nimport './UniswapV2Pair.sol';\n\ncontract UniswapV2Factory is IUniswapV2Factory {\n address public override feeTo;\n address public override feeToSetter;\n address public override migrator;\n\n mapping(address => mapping(address => address)) public override getPair;\n address[] public override allPairs;\n\n event PairCreated(address indexed token0, address indexed token1, address pair, uint);\n\n constructor(address _feeToSetter) public {\n feeToSetter = _feeToSetter;\n }\n\n function allPairsLength() external override view returns (uint) {\n return allPairs.length;\n }\n\n function pairCodeHash() external pure returns (bytes32) {\n return keccak256(type(UniswapV2Pair).creationCode);\n }\n\n function createPair(address tokenA, address tokenB) external override returns (address pair) {\n require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');\n (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);\n require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');\n require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient\n bytes memory bytecode = type(UniswapV2Pair).creationCode;\n bytes32 salt = keccak256(abi.encodePacked(token0, token1));\n assembly {\n pair := create2(0, add(bytecode, 32), mload(bytecode), salt)\n }\n UniswapV2Pair(pair).initialize(token0, token1);\n getPair[token0][token1] = pair;\n getPair[token1][token0] = pair; // populate mapping in the reverse direction\n allPairs.push(pair);\n emit PairCreated(token0, token1, pair, allPairs.length);\n }\n\n function setFeeTo(address _feeTo) external override {\n require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');\n feeTo = _feeTo;\n }\n\n function setMigrator(address _migrator) external override {\n require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');\n migrator = _migrator;\n }\n\n function setFeeToSetter(address _feeToSetter) external override {\n require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');\n feeToSetter = _feeToSetter;\n }\n\n}\n",
+ "contracts/uniswapv2/UniswapV2Pair.sol": "// SPDX-License-Identifier: GPL-3.0\n\npragma solidity =0.6.12;\n\nimport './UniswapV2ERC20.sol';\nimport './libraries/Math.sol';\nimport './libraries/UQ112x112.sol';\nimport './interfaces/IERC20.sol';\nimport './interfaces/IUniswapV2Factory.sol';\nimport './interfaces/IUniswapV2Callee.sol';\n\ninterface IMigrator {\n // Return the desired amount of liquidity token that the migrator wants.\n function desiredLiquidity() external view returns (uint256);\n}\n\ncontract UniswapV2Pair is UniswapV2ERC20 {\n using SafeMathUniswap for uint;\n using UQ112x112 for uint224;\n\n uint public constant MINIMUM_LIQUIDITY = 10**3;\n bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));\n\n address public factory;\n address public token0;\n address public token1;\n\n uint112 private reserve0; // uses single storage slot, accessible via getReserves\n uint112 private reserve1; // uses single storage slot, accessible via getReserves\n uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves\n\n uint public price0CumulativeLast;\n uint public price1CumulativeLast;\n uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event\n\n uint private unlocked = 1;\n modifier lock() {\n require(unlocked == 1, 'UniswapV2: LOCKED');\n unlocked = 0;\n _;\n unlocked = 1;\n }\n\n function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {\n _reserve0 = reserve0;\n _reserve1 = reserve1;\n _blockTimestampLast = blockTimestampLast;\n }\n\n function _safeTransfer(address token, address to, uint value) private {\n (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));\n require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');\n }\n\n event Mint(address indexed sender, uint amount0, uint amount1);\n event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);\n event Swap(\n address indexed sender,\n uint amount0In,\n uint amount1In,\n uint amount0Out,\n uint amount1Out,\n address indexed to\n );\n event Sync(uint112 reserve0, uint112 reserve1);\n\n constructor() public {\n factory = msg.sender;\n }\n\n // called once by the factory at time of deployment\n function initialize(address _token0, address _token1) external {\n require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check\n token0 = _token0;\n token1 = _token1;\n }\n\n // update reserves and, on the first call per block, price accumulators\n function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {\n require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');\n uint32 blockTimestamp = uint32(block.timestamp % 2**32);\n uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired\n if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {\n // * never overflows, and + overflow is desired\n price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;\n price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;\n }\n reserve0 = uint112(balance0);\n reserve1 = uint112(balance1);\n blockTimestampLast = blockTimestamp;\n emit Sync(reserve0, reserve1);\n }\n\n // if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k)\n function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {\n address feeTo = IUniswapV2Factory(factory).feeTo();\n feeOn = feeTo != address(0);\n uint _kLast = kLast; // gas savings\n if (feeOn) {\n if (_kLast != 0) {\n uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));\n uint rootKLast = Math.sqrt(_kLast);\n if (rootK > rootKLast) {\n uint numerator = totalSupply.mul(rootK.sub(rootKLast));\n uint denominator = rootK.mul(5).add(rootKLast);\n uint liquidity = numerator / denominator;\n if (liquidity > 0) _mint(feeTo, liquidity);\n }\n }\n } else if (_kLast != 0) {\n kLast = 0;\n }\n }\n\n // this low-level function should be called from a contract which performs important safety checks\n function mint(address to) external lock returns (uint liquidity) {\n (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings\n uint balance0 = IERC20Uniswap(token0).balanceOf(address(this));\n uint balance1 = IERC20Uniswap(token1).balanceOf(address(this));\n uint amount0 = balance0.sub(_reserve0);\n uint amount1 = balance1.sub(_reserve1);\n\n bool feeOn = _mintFee(_reserve0, _reserve1);\n uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee\n if (_totalSupply == 0) {\n address migrator = IUniswapV2Factory(factory).migrator();\n if (msg.sender == migrator) {\n liquidity = IMigrator(migrator).desiredLiquidity();\n require(liquidity > 0 && liquidity != uint256(-1), \"Bad desired liquidity\");\n } else {\n require(migrator == address(0), \"Must not have migrator\");\n liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);\n _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens\n }\n } else {\n liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);\n }\n require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');\n _mint(to, liquidity);\n\n _update(balance0, balance1, _reserve0, _reserve1);\n if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date\n emit Mint(msg.sender, amount0, amount1);\n }\n\n // this low-level function should be called from a contract which performs important safety checks\n function burn(address to) external lock returns (uint amount0, uint amount1) {\n (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings\n address _token0 = token0; // gas savings\n address _token1 = token1; // gas savings\n uint balance0 = IERC20Uniswap(_token0).balanceOf(address(this));\n uint balance1 = IERC20Uniswap(_token1).balanceOf(address(this));\n uint liquidity = balanceOf[address(this)];\n\n bool feeOn = _mintFee(_reserve0, _reserve1);\n uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee\n amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution\n amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution\n require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');\n _burn(address(this), liquidity);\n _safeTransfer(_token0, to, amount0);\n _safeTransfer(_token1, to, amount1);\n balance0 = IERC20Uniswap(_token0).balanceOf(address(this));\n balance1 = IERC20Uniswap(_token1).balanceOf(address(this));\n\n _update(balance0, balance1, _reserve0, _reserve1);\n if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date\n emit Burn(msg.sender, amount0, amount1, to);\n }\n\n // this low-level function should be called from a contract which performs important safety checks\n function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {\n require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');\n (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings\n require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');\n\n uint balance0;\n uint balance1;\n { // scope for _token{0,1}, avoids stack too deep errors\n address _token0 = token0;\n address _token1 = token1;\n require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');\n if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens\n if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens\n if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);\n balance0 = IERC20Uniswap(_token0).balanceOf(address(this));\n balance1 = IERC20Uniswap(_token1).balanceOf(address(this));\n }\n uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;\n uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;\n require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');\n { // scope for reserve{0,1}Adjusted, avoids stack too deep errors\n uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));\n uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));\n require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');\n }\n\n _update(balance0, balance1, _reserve0, _reserve1);\n emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);\n }\n\n // force balances to match reserves\n function skim(address to) external lock {\n address _token0 = token0; // gas savings\n address _token1 = token1; // gas savings\n _safeTransfer(_token0, to, IERC20Uniswap(_token0).balanceOf(address(this)).sub(reserve0));\n _safeTransfer(_token1, to, IERC20Uniswap(_token1).balanceOf(address(this)).sub(reserve1));\n }\n\n // force reserves to match balances\n function sync() external lock {\n _update(IERC20Uniswap(token0).balanceOf(address(this)), IERC20Uniswap(token1).balanceOf(address(this)), reserve0, reserve1);\n }\n}\n",
+ "contracts/uniswapv2/UniswapV2Router02.sol": "// SPDX-License-Identifier: GPL-3.0\n\npragma solidity =0.6.12;\n\nimport './libraries/UniswapV2Library.sol';\nimport './libraries/SafeMath.sol';\nimport './libraries/TransferHelper.sol';\nimport './interfaces/IUniswapV2Router02.sol';\nimport './interfaces/IUniswapV2Factory.sol';\nimport './interfaces/IERC20.sol';\nimport './interfaces/IWETH.sol';\n\ncontract UniswapV2Router02 is IUniswapV2Router02 {\n using SafeMathUniswap for uint;\n\n address public immutable override factory;\n address public immutable override WETH;\n\n modifier ensure(uint deadline) {\n require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');\n _;\n }\n\n constructor(address _factory, address _WETH) public {\n factory = _factory;\n WETH = _WETH;\n }\n\n receive() external payable {\n assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract\n }\n\n // **** ADD LIQUIDITY ****\n function _addLiquidity(\n address tokenA,\n address tokenB,\n uint amountADesired,\n uint amountBDesired,\n uint amountAMin,\n uint amountBMin\n ) internal virtual returns (uint amountA, uint amountB) {\n // create the pair if it doesn't exist yet\n if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {\n IUniswapV2Factory(factory).createPair(tokenA, tokenB);\n }\n (uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);\n if (reserveA == 0 && reserveB == 0) {\n (amountA, amountB) = (amountADesired, amountBDesired);\n } else {\n uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);\n if (amountBOptimal <= amountBDesired) {\n require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');\n (amountA, amountB) = (amountADesired, amountBOptimal);\n } else {\n uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);\n assert(amountAOptimal <= amountADesired);\n require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');\n (amountA, amountB) = (amountAOptimal, amountBDesired);\n }\n }\n }\n function addLiquidity(\n address tokenA,\n address tokenB,\n uint amountADesired,\n uint amountBDesired,\n uint amountAMin,\n uint amountBMin,\n address to,\n uint deadline\n ) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {\n (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);\n address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);\n TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);\n TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);\n liquidity = IUniswapV2Pair(pair).mint(to);\n }\n function addLiquidityETH(\n address token,\n uint amountTokenDesired,\n uint amountTokenMin,\n uint amountETHMin,\n address to,\n uint deadline\n ) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {\n (amountToken, amountETH) = _addLiquidity(\n token,\n WETH,\n amountTokenDesired,\n msg.value,\n amountTokenMin,\n amountETHMin\n );\n address pair = UniswapV2Library.pairFor(factory, token, WETH);\n TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);\n IWETH(WETH).deposit{value: amountETH}();\n assert(IWETH(WETH).transfer(pair, amountETH));\n liquidity = IUniswapV2Pair(pair).mint(to);\n // refund dust eth, if any\n if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);\n }\n\n // **** REMOVE LIQUIDITY ****\n function removeLiquidity(\n address tokenA,\n address tokenB,\n uint liquidity,\n uint amountAMin,\n uint amountBMin,\n address to,\n uint deadline\n ) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {\n address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);\n IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair\n (uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);\n (address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);\n (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);\n require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');\n require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');\n }\n function removeLiquidityETH(\n address token,\n uint liquidity,\n uint amountTokenMin,\n uint amountETHMin,\n address to,\n uint deadline\n ) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {\n (amountToken, amountETH) = removeLiquidity(\n token,\n WETH,\n liquidity,\n amountTokenMin,\n amountETHMin,\n address(this),\n deadline\n );\n TransferHelper.safeTransfer(token, to, amountToken);\n IWETH(WETH).withdraw(amountETH);\n TransferHelper.safeTransferETH(to, amountETH);\n }\n function removeLiquidityWithPermit(\n address tokenA,\n address tokenB,\n uint liquidity,\n uint amountAMin,\n uint amountBMin,\n address to,\n uint deadline,\n bool approveMax, uint8 v, bytes32 r, bytes32 s\n ) external virtual override returns (uint amountA, uint amountB) {\n address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);\n uint value = approveMax ? uint(-1) : liquidity;\n IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);\n (amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);\n }\n function removeLiquidityETHWithPermit(\n address token,\n uint liquidity,\n uint amountTokenMin,\n uint amountETHMin,\n address to,\n uint deadline,\n bool approveMax, uint8 v, bytes32 r, bytes32 s\n ) external virtual override returns (uint amountToken, uint amountETH) {\n address pair = UniswapV2Library.pairFor(factory, token, WETH);\n uint value = approveMax ? uint(-1) : liquidity;\n IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);\n (amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);\n }\n\n // **** REMOVE LIQUIDITY (supporting fee-on-transfer tokens) ****\n function removeLiquidityETHSupportingFeeOnTransferTokens(\n address token,\n uint liquidity,\n uint amountTokenMin,\n uint amountETHMin,\n address to,\n uint deadline\n ) public virtual override ensure(deadline) returns (uint amountETH) {\n (, amountETH) = removeLiquidity(\n token,\n WETH,\n liquidity,\n amountTokenMin,\n amountETHMin,\n address(this),\n deadline\n );\n TransferHelper.safeTransfer(token, to, IERC20Uniswap(token).balanceOf(address(this)));\n IWETH(WETH).withdraw(amountETH);\n TransferHelper.safeTransferETH(to, amountETH);\n }\n function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(\n address token,\n uint liquidity,\n uint amountTokenMin,\n uint amountETHMin,\n address to,\n uint deadline,\n bool approveMax, uint8 v, bytes32 r, bytes32 s\n ) external virtual override returns (uint amountETH) {\n address pair = UniswapV2Library.pairFor(factory, token, WETH);\n uint value = approveMax ? uint(-1) : liquidity;\n IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);\n amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(\n token, liquidity, amountTokenMin, amountETHMin, to, deadline\n );\n }\n\n // **** SWAP ****\n // requires the initial amount to have already been sent to the first pair\n function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {\n for (uint i; i < path.length - 1; i++) {\n (address input, address output) = (path[i], path[i + 1]);\n (address token0,) = UniswapV2Library.sortTokens(input, output);\n uint amountOut = amounts[i + 1];\n (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));\n address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;\n IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(\n amount0Out, amount1Out, to, new bytes(0)\n );\n }\n }\n function swapExactTokensForTokens(\n uint amountIn,\n uint amountOutMin,\n address[] calldata path,\n address to,\n uint deadline\n ) external virtual override ensure(deadline) returns (uint[] memory amounts) {\n amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);\n require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');\n TransferHelper.safeTransferFrom(\n path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]\n );\n _swap(amounts, path, to);\n }\n function swapTokensForExactTokens(\n uint amountOut,\n uint amountInMax,\n address[] calldata path,\n address to,\n uint deadline\n ) external virtual override ensure(deadline) returns (uint[] memory amounts) {\n amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);\n require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');\n TransferHelper.safeTransferFrom(\n path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]\n );\n _swap(amounts, path, to);\n }\n function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)\n external\n virtual\n override\n payable\n ensure(deadline)\n returns (uint[] memory amounts)\n {\n require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');\n amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);\n require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');\n IWETH(WETH).deposit{value: amounts[0]}();\n assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));\n _swap(amounts, path, to);\n }\n function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)\n external\n virtual\n override\n ensure(deadline)\n returns (uint[] memory amounts)\n {\n require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');\n amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);\n require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');\n TransferHelper.safeTransferFrom(\n path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]\n );\n _swap(amounts, path, address(this));\n IWETH(WETH).withdraw(amounts[amounts.length - 1]);\n TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);\n }\n function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)\n external\n virtual\n override\n ensure(deadline)\n returns (uint[] memory amounts)\n {\n require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');\n amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);\n require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');\n TransferHelper.safeTransferFrom(\n path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]\n );\n _swap(amounts, path, address(this));\n IWETH(WETH).withdraw(amounts[amounts.length - 1]);\n TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);\n }\n function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)\n external\n virtual\n override\n payable\n ensure(deadline)\n returns (uint[] memory amounts)\n {\n require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');\n amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);\n require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');\n IWETH(WETH).deposit{value: amounts[0]}();\n assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));\n _swap(amounts, path, to);\n // refund dust eth, if any\n if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);\n }\n\n // **** SWAP (supporting fee-on-transfer tokens) ****\n // requires the initial amount to have already been sent to the first pair\n function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual {\n for (uint i; i < path.length - 1; i++) {\n (address input, address output) = (path[i], path[i + 1]);\n (address token0,) = UniswapV2Library.sortTokens(input, output);\n IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output));\n uint amountInput;\n uint amountOutput;\n { // scope to avoid stack too deep errors\n (uint reserve0, uint reserve1,) = pair.getReserves();\n (uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);\n amountInput = IERC20Uniswap(input).balanceOf(address(pair)).sub(reserveInput);\n amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);\n }\n (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0));\n address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;\n pair.swap(amount0Out, amount1Out, to, new bytes(0));\n }\n }\n function swapExactTokensForTokensSupportingFeeOnTransferTokens(\n uint amountIn,\n uint amountOutMin,\n address[] calldata path,\n address to,\n uint deadline\n ) external virtual override ensure(deadline) {\n TransferHelper.safeTransferFrom(\n path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn\n );\n uint balanceBefore = IERC20Uniswap(path[path.length - 1]).balanceOf(to);\n _swapSupportingFeeOnTransferTokens(path, to);\n require(\n IERC20Uniswap(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,\n 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'\n );\n }\n function swapExactETHForTokensSupportingFeeOnTransferTokens(\n uint amountOutMin,\n address[] calldata path,\n address to,\n uint deadline\n )\n external\n virtual\n override\n payable\n ensure(deadline)\n {\n require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');\n uint amountIn = msg.value;\n IWETH(WETH).deposit{value: amountIn}();\n assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn));\n uint balanceBefore = IERC20Uniswap(path[path.length - 1]).balanceOf(to);\n _swapSupportingFeeOnTransferTokens(path, to);\n require(\n IERC20Uniswap(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,\n 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'\n );\n }\n function swapExactTokensForETHSupportingFeeOnTransferTokens(\n uint amountIn,\n uint amountOutMin,\n address[] calldata path,\n address to,\n uint deadline\n )\n external\n virtual\n override\n ensure(deadline)\n {\n require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');\n TransferHelper.safeTransferFrom(\n path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn\n );\n _swapSupportingFeeOnTransferTokens(path, address(this));\n uint amountOut = IERC20Uniswap(WETH).balanceOf(address(this));\n require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');\n IWETH(WETH).withdraw(amountOut);\n TransferHelper.safeTransferETH(to, amountOut);\n }\n\n // **** LIBRARY FUNCTIONS ****\n function quote(uint amountA, uint reserveA, uint reserveB) public pure virtual override returns (uint amountB) {\n return UniswapV2Library.quote(amountA, reserveA, reserveB);\n }\n\n function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut)\n public\n pure\n virtual\n override\n returns (uint amountOut)\n {\n return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);\n }\n\n function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut)\n public\n pure\n virtual\n override\n returns (uint amountIn)\n {\n return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);\n }\n\n function getAmountsOut(uint amountIn, address[] memory path)\n public\n view\n virtual\n override\n returns (uint[] memory amounts)\n {\n return UniswapV2Library.getAmountsOut(factory, amountIn, path);\n }\n\n function getAmountsIn(uint amountOut, address[] memory path)\n public\n view\n virtual\n override\n returns (uint[] memory amounts)\n {\n return UniswapV2Library.getAmountsIn(factory, amountOut, path);\n }\n}\n",
+ "contracts/uniswapv2/interfaces/IERC20.sol": "// SPDX-License-Identifier: GPL-3.0\n\npragma solidity >=0.5.0;\n\ninterface IERC20Uniswap {\n event Approval(address indexed owner, address indexed spender, uint value);\n event Transfer(address indexed from, address indexed to, uint value);\n\n function name() external view returns (string memory);\n function symbol() external view returns (string memory);\n function decimals() external view returns (uint8);\n function totalSupply() external view returns (uint);\n function balanceOf(address owner) external view returns (uint);\n function allowance(address owner, address spender) external view returns (uint);\n\n function approve(address spender, uint value) external returns (bool);\n function transfer(address to, uint value) external returns (bool);\n function transferFrom(address from, address to, uint value) external returns (bool);\n}\n",
+ "contracts/uniswapv2/interfaces/IUniswapV2Callee.sol": "// SPDX-License-Identifier: GPL-3.0\n\npragma solidity >=0.5.0;\n\ninterface IUniswapV2Callee {\n function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external;\n}\n",
+ "contracts/uniswapv2/interfaces/IUniswapV2ERC20.sol": "// SPDX-License-Identifier: GPL-3.0\n\npragma solidity >=0.5.0;\n\ninterface IUniswapV2ERC20 {\n event Approval(address indexed owner, address indexed spender, uint value);\n event Transfer(address indexed from, address indexed to, uint value);\n\n function name() external pure returns (string memory);\n function symbol() external pure returns (string memory);\n function decimals() external pure returns (uint8);\n function totalSupply() external view returns (uint);\n function balanceOf(address owner) external view returns (uint);\n function allowance(address owner, address spender) external view returns (uint);\n\n function approve(address spender, uint value) external returns (bool);\n function transfer(address to, uint value) external returns (bool);\n function transferFrom(address from, address to, uint value) external returns (bool);\n\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n function PERMIT_TYPEHASH() external pure returns (bytes32);\n function nonces(address owner) external view returns (uint);\n\n function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;\n}",
+ "contracts/uniswapv2/interfaces/IUniswapV2Factory.sol": "// SPDX-License-Identifier: GPL-3.0\n\npragma solidity >=0.5.0;\n\ninterface IUniswapV2Factory {\n event PairCreated(address indexed token0, address indexed token1, address pair, uint);\n\n function feeTo() external view returns (address);\n function feeToSetter() external view returns (address);\n function migrator() external view returns (address);\n\n function getPair(address tokenA, address tokenB) external view returns (address pair);\n function allPairs(uint) external view returns (address pair);\n function allPairsLength() external view returns (uint);\n\n function createPair(address tokenA, address tokenB) external returns (address pair);\n\n function setFeeTo(address) external;\n function setFeeToSetter(address) external;\n function setMigrator(address) external;\n}\n",
+ "contracts/uniswapv2/interfaces/IUniswapV2Pair.sol": "// SPDX-License-Identifier: GPL-3.0\n\npragma solidity >=0.5.0;\n\ninterface IUniswapV2Pair {\n event Approval(address indexed owner, address indexed spender, uint value);\n event Transfer(address indexed from, address indexed to, uint value);\n\n function name() external pure returns (string memory);\n function symbol() external pure returns (string memory);\n function decimals() external pure returns (uint8);\n function totalSupply() external view returns (uint);\n function balanceOf(address owner) external view returns (uint);\n function allowance(address owner, address spender) external view returns (uint);\n\n function approve(address spender, uint value) external returns (bool);\n function transfer(address to, uint value) external returns (bool);\n function transferFrom(address from, address to, uint value) external returns (bool);\n\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n function PERMIT_TYPEHASH() external pure returns (bytes32);\n function nonces(address owner) external view returns (uint);\n\n function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;\n\n event Mint(address indexed sender, uint amount0, uint amount1);\n event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);\n event Swap(\n address indexed sender,\n uint amount0In,\n uint amount1In,\n uint amount0Out,\n uint amount1Out,\n address indexed to\n );\n event Sync(uint112 reserve0, uint112 reserve1);\n\n function MINIMUM_LIQUIDITY() external pure returns (uint);\n function factory() external view returns (address);\n function token0() external view returns (address);\n function token1() external view returns (address);\n function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);\n function price0CumulativeLast() external view returns (uint);\n function price1CumulativeLast() external view returns (uint);\n function kLast() external view returns (uint);\n\n function mint(address to) external returns (uint liquidity);\n function burn(address to) external returns (uint amount0, uint amount1);\n function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;\n function skim(address to) external;\n function sync() external;\n\n function initialize(address, address) external;\n}",
+ "contracts/uniswapv2/interfaces/IUniswapV2Router01.sol": "// SPDX-License-Identifier: GPL-3.0\n\npragma solidity >=0.6.2;\n\ninterface IUniswapV2Router01 {\n function factory() external pure returns (address);\n function WETH() external pure returns (address);\n\n function addLiquidity(\n address tokenA,\n address tokenB,\n uint amountADesired,\n uint amountBDesired,\n uint amountAMin,\n uint amountBMin,\n address to,\n uint deadline\n ) external returns (uint amountA, uint amountB, uint liquidity);\n function addLiquidityETH(\n address token,\n uint amountTokenDesired,\n uint amountTokenMin,\n uint amountETHMin,\n address to,\n uint deadline\n ) external payable returns (uint amountToken, uint amountETH, uint liquidity);\n function removeLiquidity(\n address tokenA,\n address tokenB,\n uint liquidity,\n uint amountAMin,\n uint amountBMin,\n address to,\n uint deadline\n ) external returns (uint amountA, uint amountB);\n function removeLiquidityETH(\n address token,\n uint liquidity,\n uint amountTokenMin,\n uint amountETHMin,\n address to,\n uint deadline\n ) external returns (uint amountToken, uint amountETH);\n function removeLiquidityWithPermit(\n address tokenA,\n address tokenB,\n uint liquidity,\n uint amountAMin,\n uint amountBMin,\n address to,\n uint deadline,\n bool approveMax, uint8 v, bytes32 r, bytes32 s\n ) external returns (uint amountA, uint amountB);\n function removeLiquidityETHWithPermit(\n address token,\n uint liquidity,\n uint amountTokenMin,\n uint amountETHMin,\n address to,\n uint deadline,\n bool approveMax, uint8 v, bytes32 r, bytes32 s\n ) external returns (uint amountToken, uint amountETH);\n function swapExactTokensForTokens(\n uint amountIn,\n uint amountOutMin,\n address[] calldata path,\n address to,\n uint deadline\n ) external returns (uint[] memory amounts);\n function swapTokensForExactTokens(\n uint amountOut,\n uint amountInMax,\n address[] calldata path,\n address to,\n uint deadline\n ) external returns (uint[] memory amounts);\n function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)\n external\n payable\n returns (uint[] memory amounts);\n function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)\n external\n returns (uint[] memory amounts);\n function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)\n external\n returns (uint[] memory amounts);\n function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)\n external\n payable\n returns (uint[] memory amounts);\n\n function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);\n function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);\n function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);\n function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);\n function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);\n}",
+ "contracts/uniswapv2/interfaces/IUniswapV2Router02.sol": "// SPDX-License-Identifier: GPL-3.0\n\npragma solidity >=0.6.2;\n\nimport './IUniswapV2Router01.sol';\n\ninterface IUniswapV2Router02 is IUniswapV2Router01 {\n function removeLiquidityETHSupportingFeeOnTransferTokens(\n address token,\n uint liquidity,\n uint amountTokenMin,\n uint amountETHMin,\n address to,\n uint deadline\n ) external returns (uint amountETH);\n function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(\n address token,\n uint liquidity,\n uint amountTokenMin,\n uint amountETHMin,\n address to,\n uint deadline,\n bool approveMax, uint8 v, bytes32 r, bytes32 s\n ) external returns (uint amountETH);\n\n function swapExactTokensForTokensSupportingFeeOnTransferTokens(\n uint amountIn,\n uint amountOutMin,\n address[] calldata path,\n address to,\n uint deadline\n ) external;\n function swapExactETHForTokensSupportingFeeOnTransferTokens(\n uint amountOutMin,\n address[] calldata path,\n address to,\n uint deadline\n ) external payable;\n function swapExactTokensForETHSupportingFeeOnTransferTokens(\n uint amountIn,\n uint amountOutMin,\n address[] calldata path,\n address to,\n uint deadline\n ) external;\n}",
+ "contracts/uniswapv2/interfaces/IWETH.sol": "// SPDX-License-Identifier: GPL-3.0\n\npragma solidity >=0.5.0;\n\ninterface IWETH {\n function deposit() external payable;\n function transfer(address to, uint value) external returns (bool);\n function withdraw(uint) external;\n}",
+ "contracts/uniswapv2/libraries/Math.sol": "// SPDX-License-Identifier: GPL-3.0\n\npragma solidity =0.6.12;\n\n// a library for performing various math operations\n\nlibrary Math {\n function min(uint x, uint y) internal pure returns (uint z) {\n z = x < y ? x : y;\n }\n\n // babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)\n function sqrt(uint y) internal pure returns (uint z) {\n if (y > 3) {\n z = y;\n uint x = y / 2 + 1;\n while (x < z) {\n z = x;\n x = (y / x + x) / 2;\n }\n } else if (y != 0) {\n z = 1;\n }\n }\n}\n",
+ "contracts/uniswapv2/libraries/SafeMath.sol": "// SPDX-License-Identifier: GPL-3.0\n\npragma solidity =0.6.12;\n\n// a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math)\n\nlibrary SafeMathUniswap {\n function add(uint x, uint y) internal pure returns (uint z) {\n require((z = x + y) >= x, 'ds-math-add-overflow');\n }\n\n function sub(uint x, uint y) internal pure returns (uint z) {\n require((z = x - y) <= x, 'ds-math-sub-underflow');\n }\n\n function mul(uint x, uint y) internal pure returns (uint z) {\n require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow');\n }\n}\n",
+ "contracts/uniswapv2/libraries/TransferHelper.sol": "// SPDX-License-Identifier: GPL-3.0\n\npragma solidity >=0.6.0;\n\n// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false\nlibrary TransferHelper {\n function safeApprove(address token, address to, uint value) internal {\n // bytes4(keccak256(bytes('approve(address,uint256)')));\n (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));\n require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: APPROVE_FAILED');\n }\n\n function safeTransfer(address token, address to, uint value) internal {\n // bytes4(keccak256(bytes('transfer(address,uint256)')));\n (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));\n require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FAILED');\n }\n\n function safeTransferFrom(address token, address from, address to, uint value) internal {\n // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));\n (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));\n require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FROM_FAILED');\n }\n\n function safeTransferETH(address to, uint value) internal {\n (bool success,) = to.call{value:value}(new bytes(0));\n require(success, 'TransferHelper: ETH_TRANSFER_FAILED');\n }\n}\n",
+ "contracts/uniswapv2/libraries/UQ112x112.sol": "// SPDX-License-Identifier: GPL-3.0\n\npragma solidity =0.6.12;\n\n// a library for handling binary fixed point numbers (https://en.wikipedia.org/wiki/Q_(number_format))\n\n// range: [0, 2**112 - 1]\n// resolution: 1 / 2**112\n\nlibrary UQ112x112 {\n uint224 constant Q112 = 2**112;\n\n // encode a uint112 as a UQ112x112\n function encode(uint112 y) internal pure returns (uint224 z) {\n z = uint224(y) * Q112; // never overflows\n }\n\n // divide a UQ112x112 by a uint112, returning a UQ112x112\n function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {\n z = x / uint224(y);\n }\n}\n",
+ "contracts/uniswapv2/libraries/UniswapV2Library.sol": "// SPDX-License-Identifier: GPL-3.0\n\npragma solidity >=0.5.0;\n\nimport '../interfaces/IUniswapV2Pair.sol';\n\nimport \"./SafeMath.sol\";\n\nlibrary UniswapV2Library {\n using SafeMathUniswap for uint;\n\n // returns sorted token addresses, used to handle return values from pairs sorted in this order\n function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {\n require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES');\n (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);\n require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS');\n }\n\n // calculates the CREATE2 address for a pair without making any external calls\n function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {\n (address token0, address token1) = sortTokens(tokenA, tokenB);\n pair = address(uint(keccak256(abi.encodePacked(\n hex'ff',\n factory,\n keccak256(abi.encodePacked(token0, token1)),\n hex'e18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303' // init code hash\n ))));\n }\n\n // fetches and sorts the reserves for a pair\n function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {\n (address token0,) = sortTokens(tokenA, tokenB);\n (uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();\n (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);\n }\n\n // given some amount of an asset and pair reserves, returns an equivalent amount of the other asset\n function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {\n require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');\n require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');\n amountB = amountA.mul(reserveB) / reserveA;\n }\n\n // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset\n function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {\n require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');\n require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');\n uint amountInWithFee = amountIn.mul(997);\n uint numerator = amountInWithFee.mul(reserveOut);\n uint denominator = reserveIn.mul(1000).add(amountInWithFee);\n amountOut = numerator / denominator;\n }\n\n // given an output amount of an asset and pair reserves, returns a required input amount of the other asset\n function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {\n require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');\n require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');\n uint numerator = reserveIn.mul(amountOut).mul(1000);\n uint denominator = reserveOut.sub(amountOut).mul(997);\n amountIn = (numerator / denominator).add(1);\n }\n\n // performs chained getAmountOut calculations on any number of pairs\n function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {\n require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');\n amounts = new uint[](path.length);\n amounts[0] = amountIn;\n for (uint i; i < path.length - 1; i++) {\n (uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);\n amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);\n }\n }\n\n // performs chained getAmountIn calculations on any number of pairs\n function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) {\n require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');\n amounts = new uint[](path.length);\n amounts[amounts.length - 1] = amountOut;\n for (uint i = path.length - 1; i > 0; i--) {\n (uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]);\n amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut);\n }\n }\n}\n"
+ },
+ "abi": "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_factory\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_WETH\",\"type\":\"address\"}],\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"WETH\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"tokenA\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenB\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amountADesired\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountBDesired\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountAMin\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountBMin\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"name\":\"addLiquidity\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountA\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountB\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"liquidity\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amountTokenDesired\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountTokenMin\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountETHMin\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"name\":\"addLiquidityETH\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountToken\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountETH\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"liquidity\",\"type\":\"uint256\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"factory\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountOut\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"reserveIn\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"reserveOut\",\"type\":\"uint256\"}],\"name\":\"getAmountIn\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"reserveIn\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"reserveOut\",\"type\":\"uint256\"}],\"name\":\"getAmountOut\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountOut\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountOut\",\"type\":\"uint256\"},{\"internalType\":\"address[]\",\"name\":\"path\",\"type\":\"address[]\"}],\"name\":\"getAmountsIn\",\"outputs\":[{\"internalType\":\"uint256[]\",\"name\":\"amounts\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"},{\"internalType\":\"address[]\",\"name\":\"path\",\"type\":\"address[]\"}],\"name\":\"getAmountsOut\",\"outputs\":[{\"internalType\":\"uint256[]\",\"name\":\"amounts\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountA\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"reserveA\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"reserveB\",\"type\":\"uint256\"}],\"name\":\"quote\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountB\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"tokenA\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenB\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"liquidity\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountAMin\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountBMin\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"name\":\"removeLiquidity\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountA\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountB\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"liquidity\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountTokenMin\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountETHMin\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"name\":\"removeLiquidityETH\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountToken\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountETH\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"liquidity\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountTokenMin\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountETHMin\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"name\":\"removeLiquidityETHSupportingFeeOnTransferTokens\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountETH\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"liquidity\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountTokenMin\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountETHMin\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"approveMax\",\"type\":\"bool\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"removeLiquidityETHWithPermit\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountToken\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountETH\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"liquidity\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountTokenMin\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountETHMin\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"approveMax\",\"type\":\"bool\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"removeLiquidityETHWithPermitSupportingFeeOnTransferTokens\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountETH\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"tokenA\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenB\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"liquidity\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountAMin\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountBMin\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"approveMax\",\"type\":\"bool\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"removeLiquidityWithPermit\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountA\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountB\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountOut\",\"type\":\"uint256\"},{\"internalType\":\"address[]\",\"name\":\"path\",\"type\":\"address[]\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"name\":\"swapETHForExactTokens\",\"outputs\":[{\"internalType\":\"uint256[]\",\"name\":\"amounts\",\"type\":\"uint256[]\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountOutMin\",\"type\":\"uint256\"},{\"internalType\":\"address[]\",\"name\":\"path\",\"type\":\"address[]\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"name\":\"swapExactETHForTokens\",\"outputs\":[{\"internalType\":\"uint256[]\",\"name\":\"amounts\",\"type\":\"uint256[]\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountOutMin\",\"type\":\"uint256\"},{\"internalType\":\"address[]\",\"name\":\"path\",\"type\":\"address[]\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"name\":\"swapExactETHForTokensSupportingFeeOnTransferTokens\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountOutMin\",\"type\":\"uint256\"},{\"internalType\":\"address[]\",\"name\":\"path\",\"type\":\"address[]\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"name\":\"swapExactTokensForETH\",\"outputs\":[{\"internalType\":\"uint256[]\",\"name\":\"amounts\",\"type\":\"uint256[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountOutMin\",\"type\":\"uint256\"},{\"internalType\":\"address[]\",\"name\":\"path\",\"type\":\"address[]\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"name\":\"swapExactTokensForETHSupportingFeeOnTransferTokens\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountOutMin\",\"type\":\"uint256\"},{\"internalType\":\"address[]\",\"name\":\"path\",\"type\":\"address[]\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"name\":\"swapExactTokensForTokens\",\"outputs\":[{\"internalType\":\"uint256[]\",\"name\":\"amounts\",\"type\":\"uint256[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountOutMin\",\"type\":\"uint256\"},{\"internalType\":\"address[]\",\"name\":\"path\",\"type\":\"address[]\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"name\":\"swapExactTokensForTokensSupportingFeeOnTransferTokens\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountOut\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountInMax\",\"type\":\"uint256\"},{\"internalType\":\"address[]\",\"name\":\"path\",\"type\":\"address[]\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"name\":\"swapTokensForExactETH\",\"outputs\":[{\"internalType\":\"uint256[]\",\"name\":\"amounts\",\"type\":\"uint256[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amountOut\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountInMax\",\"type\":\"uint256\"},{\"internalType\":\"address[]\",\"name\":\"path\",\"type\":\"address[]\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"name\":\"swapTokensForExactTokens\",\"outputs\":[{\"internalType\":\"uint256[]\",\"name\":\"amounts\",\"type\":\"uint256[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"type\":\"receive\"}]",
+ "constructorArguments": "000000000000000000000000c35dadb65012ec5796536bd9864ed8773abc74c4000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d6",
+ "matchType": "PARTIAL"
+ }
+ ],
+ "sourcifySources": []
+}
\ No newline at end of file
diff --git a/apps/block_scout_web/test/support/fixture/smart_contract/eth_bytecode_db_search_all_sourcify_sources_response.json b/apps/block_scout_web/test/support/fixture/smart_contract/eth_bytecode_db_search_all_sourcify_sources_response.json
new file mode 100644
index 000000000000..8456169ebd33
--- /dev/null
+++ b/apps/block_scout_web/test/support/fixture/smart_contract/eth_bytecode_db_search_all_sourcify_sources_response.json
@@ -0,0 +1,30 @@
+{
+ "ethBytecodeDbSources": [
+ {
+ "fileName": "Test_eth.sol",
+ "contractName": "Test",
+ "compilerVersion": "v0.8.17+commit.8df45f5f",
+ "compilerSettings": "{\"libraries\":{\"Test.sol\":{}},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":199},\"outputSelection\":{\"*\":{\"\":[\"ast\"],\"*\":[\"abi\",\"evm.bytecode\",\"evm.deployedBytecode\",\"evm.methodIdentifiers\"]}}}",
+ "sourceType": "SOLIDITY",
+ "sourceFiles": {
+ "Test.sol": "// SPDX-License-Identifier: MIT\r\n\r\npragma solidity 0.8.17;\r\n\r\ncontract Test {\r\n enum E {\r\n V1, V2, V3, V4\r\n }\r\n struct A {\r\n E a;\r\n uint256[] b;\r\n B[] c;\r\n }\r\n\r\n struct B {\r\n uint256 d;\r\n uint256 e;\r\n }\r\n\r\n function get(uint256 x) external pure returns (A memory) {\r\n uint256[] memory b = new uint256[](3);\r\n b[0] = 1;\r\n b[1] = 2;\r\n b[2] = 3;\r\n B[] memory c = new B[](3);\r\n c[0] = B(1, 2);\r\n c[1] = B(3, 4);\r\n c[2] = B(5, 6);\r\n return A(E.V3, b, c);\r\n }\r\n}"
+ },
+ "abi": "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"x\",\"type\":\"uint256\"}],\"name\":\"get\",\"outputs\":[{\"components\":[{\"type\":\"uint8\"},{\"type\":\"uint256[]\"},{\"components\":[{\"type\":\"uint256\"},{\"type\":\"uint256\"}],\"type\":\"tuple[]\"}],\"internalType\":\"struct Test.A\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]",
+ "constructorArguments": "0x0000000000000000000000003e5c63644e683549055b9be8653de26e0b4cd36e",
+ "matchType": "PARTIAL"
+ }
+ ],
+ "sourcifySources": [{
+ "fileName": "Test.sol",
+ "contractName": "Test",
+ "compilerVersion": "v0.8.17+commit.8df45f5f",
+ "compilerSettings": "{\"libraries\":{\"Test.sol\":{}},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":199},\"outputSelection\":{\"*\":{\"\":[\"ast\"],\"*\":[\"abi\",\"evm.bytecode\",\"evm.deployedBytecode\",\"evm.methodIdentifiers\"]}}}",
+ "sourceType": "SOLIDITY",
+ "sourceFiles": {
+ "Test.sol": "// SPDX-License-Identifier: MIT\r\n\r\npragma solidity 0.8.17;\r\n\r\ncontract Test {\r\n enum E {\r\n V1, V2, V3, V4\r\n }\r\n struct A {\r\n E a;\r\n uint256[] b;\r\n B[] c;\r\n }\r\n\r\n struct B {\r\n uint256 d;\r\n uint256 e;\r\n }\r\n\r\n function get(uint256 x) external pure returns (A memory) {\r\n uint256[] memory b = new uint256[](3);\r\n b[0] = 1;\r\n b[1] = 2;\r\n b[2] = 3;\r\n B[] memory c = new B[](3);\r\n c[0] = B(1, 2);\r\n c[1] = B(3, 4);\r\n c[2] = B(5, 6);\r\n return A(E.V3, b, c);\r\n }\r\n}"
+ },
+ "abi": "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"x\",\"type\":\"uint256\"}],\"name\":\"get\",\"outputs\":[{\"components\":[{\"type\":\"uint8\"},{\"type\":\"uint256[]\"},{\"components\":[{\"type\":\"uint256\"},{\"type\":\"uint256\"}],\"type\":\"tuple[]\"}],\"internalType\":\"struct Test.A\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]",
+ "constructorArguments": "0x0000000000000000000000003e5c63644e683549055b9be8653de26e0b4cd36e",
+ "matchType": "PARTIAL"
+ }]
+ }
\ No newline at end of file
diff --git a/apps/block_scout_web/test/support/fixture/smart_contract/eth_bytecode_db_search_all_sourcify_sources_with_libs_response.json b/apps/block_scout_web/test/support/fixture/smart_contract/eth_bytecode_db_search_all_sourcify_sources_with_libs_response.json
new file mode 100644
index 000000000000..494c7897b819
--- /dev/null
+++ b/apps/block_scout_web/test/support/fixture/smart_contract/eth_bytecode_db_search_all_sourcify_sources_with_libs_response.json
@@ -0,0 +1,47 @@
+{
+ "ethBytecodeDbSources": [],
+ "sourcifySources": [
+ {
+ "fileName": "src/zkbob/ZkBobPool.sol",
+ "contractName": "ZkBobPool",
+ "compilerVersion": "0.8.15+commit.e14f2714",
+ "compilerSettings": "{\"evmVersion\":\"london\",\"libraries\":{\"lib/base58-solidity/contracts/Base58.sol:Base58\":\"0x22de6b06544ee5cd907813a04bcded149a2f49d2\",\"src/libraries/ZkAddress.sol:ZkAddress\":\"0x019d3788f00a7087234f3844cb1cece1f9982b7a\"},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@base58-solidity/=lib/base58-solidity/contracts/\",\":@gnosis/=lib/@gnosis/\",\":@gnosis/auction/=lib/@gnosis/auction/contracts/\",\":@openzeppelin/=lib/@openzeppelin/contracts/\",\":@openzeppelin/contracts/=lib/@openzeppelin/contracts/contracts/\",\":@uniswap/=lib/@uniswap/\",\":base58-solidity/=lib/base58-solidity/contracts/\",\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":forge-std/=lib/forge-std/src/\"]}",
+ "sourceType": "SOLIDITY",
+ "sourceFiles": {
+ "lib/_openzeppelin/contracts/contracts/access/Ownable.sol": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor() {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions anymore. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby removing any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n",
+ "lib/_openzeppelin/contracts/contracts/token/ERC20/IERC20.sol": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) external returns (bool);\n}\n",
+ "lib/_openzeppelin/contracts/contracts/token/ERC20/extensions/draft-IERC20Permit.sol": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\n *\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\n * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't\n * need to send a transaction, and thus is not required to hold Ether at all.\n */\ninterface IERC20Permit {\n /**\n * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,\n * given ``owner``'s signed approval.\n *\n * IMPORTANT: The same issues {IERC20-approve} has related to transaction\n * ordering also apply here.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `deadline` must be a timestamp in the future.\n * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`\n * over the EIP712-formatted function arguments.\n * - the signature must use ``owner``'s current nonce (see {nonces}).\n *\n * For more information on the signature format, see the\n * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP\n * section].\n */\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n /**\n * @dev Returns the current nonce for `owner`. This value must be\n * included whenever a signature is generated for {permit}.\n *\n * Every successful call to {permit} increases ``owner``'s nonce by one. This\n * prevents a signature from being used multiple times.\n */\n function nonces(address owner) external view returns (uint256);\n\n /**\n * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.\n */\n // solhint-disable-next-line func-name-mixedcase\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n}\n",
+ "lib/_openzeppelin/contracts/contracts/token/ERC20/utils/SafeERC20.sol": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\nimport \"../extensions/draft-IERC20Permit.sol\";\nimport \"../../../utils/Address.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n using Address for address;\n\n function safeTransfer(\n IERC20 token,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\n }\n\n function safeTransferFrom(\n IERC20 token,\n address from,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\n }\n\n /**\n * @dev Deprecated. This function has issues similar to the ones found in\n * {IERC20-approve}, and its usage is discouraged.\n *\n * Whenever possible, use {safeIncreaseAllowance} and\n * {safeDecreaseAllowance} instead.\n */\n function safeApprove(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n // safeApprove should only be called when setting an initial allowance,\n // or when resetting it to zero. To increase and decrease it, use\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\n require(\n (value == 0) || (token.allowance(address(this), spender) == 0),\n \"SafeERC20: approve from non-zero to non-zero allowance\"\n );\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\n }\n\n function safeIncreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n uint256 newAllowance = token.allowance(address(this), spender) + value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n\n function safeDecreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n unchecked {\n uint256 oldAllowance = token.allowance(address(this), spender);\n require(oldAllowance >= value, \"SafeERC20: decreased allowance below zero\");\n uint256 newAllowance = oldAllowance - value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n }\n\n function safePermit(\n IERC20Permit token,\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal {\n uint256 nonceBefore = token.nonces(owner);\n token.permit(owner, spender, value, deadline, v, r, s);\n uint256 nonceAfter = token.nonces(owner);\n require(nonceAfter == nonceBefore + 1, \"SafeERC20: permit did not succeed\");\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n */\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\n // the target address contains contract code and also asserts for success in the low-level call.\n\n bytes memory returndata = address(token).functionCall(data, \"SafeERC20: low-level call failed\");\n if (returndata.length > 0) {\n // Return data is optional\n require(abi.decode(returndata, (bool)), \"SafeERC20: ERC20 operation did not succeed\");\n }\n }\n}\n",
+ "lib/_openzeppelin/contracts/contracts/utils/Address.sol": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\n *\n * _Available since v4.8._\n */\n function verifyCallResultFromTarget(\n address target,\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n if (success) {\n if (returndata.length == 0) {\n // only check isContract if the call was successful and the return data is empty\n // otherwise we already know that it was a contract\n require(isContract(target), \"Address: call to non-contract\");\n }\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n /**\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason or using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n /// @solidity memory-safe-assembly\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n}\n",
+ "lib/_openzeppelin/contracts/contracts/utils/Context.sol": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n",
+ "lib/_openzeppelin/contracts/contracts/utils/Strings.sol": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n bytes16 private constant _HEX_SYMBOLS = \"0123456789abcdef\";\n uint8 private constant _ADDRESS_LENGTH = 20;\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n // Inspired by OraclizeAPI's implementation - MIT licence\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\n\n if (value == 0) {\n return \"0\";\n }\n uint256 temp = value;\n uint256 digits;\n while (temp != 0) {\n digits++;\n temp /= 10;\n }\n bytes memory buffer = new bytes(digits);\n while (value != 0) {\n digits -= 1;\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\n value /= 10;\n }\n return string(buffer);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n if (value == 0) {\n return \"0x00\";\n }\n uint256 temp = value;\n uint256 length = 0;\n while (temp != 0) {\n length++;\n temp >>= 8;\n }\n return toHexString(value, length);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\n value >>= 4;\n }\n require(value == 0, \"Strings: hex length insufficient\");\n return string(buffer);\n }\n\n /**\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.\n */\n function toHexString(address addr) internal pure returns (string memory) {\n return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);\n }\n}\n",
+ "lib/_openzeppelin/contracts/contracts/utils/cryptography/ECDSA.sol": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (utils/cryptography/ECDSA.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../Strings.sol\";\n\n/**\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\n *\n * These functions can be used to verify that a message was signed by the holder\n * of the private keys of a given address.\n */\nlibrary ECDSA {\n enum RecoverError {\n NoError,\n InvalidSignature,\n InvalidSignatureLength,\n InvalidSignatureS,\n InvalidSignatureV\n }\n\n function _throwError(RecoverError error) private pure {\n if (error == RecoverError.NoError) {\n return; // no error: do nothing\n } else if (error == RecoverError.InvalidSignature) {\n revert(\"ECDSA: invalid signature\");\n } else if (error == RecoverError.InvalidSignatureLength) {\n revert(\"ECDSA: invalid signature length\");\n } else if (error == RecoverError.InvalidSignatureS) {\n revert(\"ECDSA: invalid signature 's' value\");\n } else if (error == RecoverError.InvalidSignatureV) {\n revert(\"ECDSA: invalid signature 'v' value\");\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature` or error string. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n *\n * Documentation for signature generation:\n * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]\n * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]\n *\n * _Available since v4.3._\n */\n function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {\n // Check the signature length\n // - case 65: r,s,v signature (standard)\n // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._\n if (signature.length == 65) {\n bytes32 r;\n bytes32 s;\n uint8 v;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n /// @solidity memory-safe-assembly\n assembly {\n r := mload(add(signature, 0x20))\n s := mload(add(signature, 0x40))\n v := byte(0, mload(add(signature, 0x60)))\n }\n return tryRecover(hash, v, r, s);\n } else if (signature.length == 64) {\n bytes32 r;\n bytes32 vs;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n /// @solidity memory-safe-assembly\n assembly {\n r := mload(add(signature, 0x20))\n vs := mload(add(signature, 0x40))\n }\n return tryRecover(hash, r, vs);\n } else {\n return (address(0), RecoverError.InvalidSignatureLength);\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature`. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n */\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, signature);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.\n *\n * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address, RecoverError) {\n bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);\n uint8 v = uint8((uint256(vs) >> 255) + 27);\n return tryRecover(hash, v, r, s);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.\n *\n * _Available since v4.2._\n */\n function recover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, r, vs);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `v`,\n * `r` and `s` signature fields separately.\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address, RecoverError) {\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\n // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\n //\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\n // these malleable signatures as well.\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\n return (address(0), RecoverError.InvalidSignatureS);\n }\n if (v != 27 && v != 28) {\n return (address(0), RecoverError.InvalidSignatureV);\n }\n\n // If the signature is valid (and not malleable), return the signer address\n address signer = ecrecover(hash, v, r, s);\n if (signer == address(0)) {\n return (address(0), RecoverError.InvalidSignature);\n }\n\n return (signer, RecoverError.NoError);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `v`,\n * `r` and `s` signature fields separately.\n */\n function recover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, v, r, s);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from a `hash`. This\n * produces hash corresponding to the one signed with the\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\n * JSON-RPC method as part of EIP-191.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {\n // 32 is the length in bytes of hash,\n // enforced by the type signature above\n return keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", hash));\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from `s`. This\n * produces hash corresponding to the one signed with the\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\n * JSON-RPC method as part of EIP-191.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n\", Strings.toString(s.length), s));\n }\n\n /**\n * @dev Returns an Ethereum Signed Typed Data, created from a\n * `domainSeparator` and a `structHash`. This produces hash corresponding\n * to the one signed with the\n * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]\n * JSON-RPC method as part of EIP-712.\n *\n * See {recover}.\n */\n function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19\\x01\", domainSeparator, structHash));\n }\n}\n",
+ "lib/_uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity >=0.5.0;\n\n/// @title Callback for IUniswapV3PoolActions#swap\n/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface\ninterface IUniswapV3SwapCallback {\n /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.\n /// @dev In the implementation you must pay the pool tokens owed for the swap.\n /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.\n /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.\n /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by\n /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.\n /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by\n /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.\n /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call\n function uniswapV3SwapCallback(\n int256 amount0Delta,\n int256 amount1Delta,\n bytes calldata data\n ) external;\n}\n",
+ "lib/_uniswap/v3-periphery/contracts/interfaces/IPeripheryImmutableState.sol": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity >=0.5.0;\n\n/// @title Immutable state\n/// @notice Functions that return immutable state of the router\ninterface IPeripheryImmutableState {\n /// @return Returns the address of the Uniswap V3 factory\n function factory() external view returns (address);\n\n /// @return Returns the address of WETH9\n function WETH9() external view returns (address);\n}\n",
+ "lib/_uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity >=0.7.5;\npragma abicoder v2;\n\nimport '@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol';\n\n/// @title Router token swapping functionality\n/// @notice Functions for swapping tokens via Uniswap V3\ninterface ISwapRouter is IUniswapV3SwapCallback {\n struct ExactInputSingleParams {\n address tokenIn;\n address tokenOut;\n uint24 fee;\n address recipient;\n uint256 deadline;\n uint256 amountIn;\n uint256 amountOutMinimum;\n uint160 sqrtPriceLimitX96;\n }\n\n /// @notice Swaps `amountIn` of one token for as much as possible of another token\n /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata\n /// @return amountOut The amount of the received token\n function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);\n\n struct ExactInputParams {\n bytes path;\n address recipient;\n uint256 deadline;\n uint256 amountIn;\n uint256 amountOutMinimum;\n }\n\n /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path\n /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata\n /// @return amountOut The amount of the received token\n function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);\n\n struct ExactOutputSingleParams {\n address tokenIn;\n address tokenOut;\n uint24 fee;\n address recipient;\n uint256 deadline;\n uint256 amountOut;\n uint256 amountInMaximum;\n uint160 sqrtPriceLimitX96;\n }\n\n /// @notice Swaps as little as possible of one token for `amountOut` of another token\n /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata\n /// @return amountIn The amount of the input token\n function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn);\n\n struct ExactOutputParams {\n bytes path;\n address recipient;\n uint256 deadline;\n uint256 amountOut;\n uint256 amountInMaximum;\n }\n\n /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)\n /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata\n /// @return amountIn The amount of the input token\n function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn);\n}\n",
+ "lib/_uniswap/v3-periphery/contracts/interfaces/external/IWETH9.sol": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity =0.8.15;\n\nimport '@openzeppelin/contracts/token/ERC20/IERC20.sol';\n\n/// @title Interface for WETH9\ninterface IWETH9 is IERC20 {\n /// @notice Deposit ether to get wrapped ether\n function deposit() external payable;\n\n /// @notice Withdraw wrapped ether to get ether\n function withdraw(uint256) external;\n}\n",
+ "src/interfaces/IBatchDepositVerifier.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.15;\n\ninterface IBatchDepositVerifier {\n function verifyProof(uint256[1] memory input, uint256[8] memory p) external view returns (bool);\n}\n",
+ "src/interfaces/IERC20Permit.sol": "// SPDX-License-Identifier: CC0-1.0\n\npragma solidity 0.8.15;\n\ninterface IERC20Permit {\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n )\n external;\n\n function nonces(address owner) external view returns (uint256);\n\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n function PERMIT_TYPEHASH() external view returns (bytes32);\n\n function SALTED_PERMIT_TYPEHASH() external view returns (bytes32);\n\n function receiveWithPermit(\n address _holder,\n uint256 _value,\n uint256 _deadline,\n uint8 _v,\n bytes32 _r,\n bytes32 _s\n )\n external;\n\n function receiveWithSaltedPermit(\n address _holder,\n uint256 _value,\n uint256 _deadline,\n bytes32 _salt,\n uint8 _v,\n bytes32 _r,\n bytes32 _s\n )\n external;\n}\n",
+ "src/interfaces/IMintableERC20.sol": "// SPDX-License-Identifier: CC0-1.0\n\npragma solidity 0.8.15;\n\ninterface IMintableERC20 {\n function mint(address to, uint256 amount) external;\n}\n",
+ "src/interfaces/IOperatorManager.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.15;\n\ninterface IOperatorManager {\n function isOperator(address _addr) external view returns (bool);\n\n function isOperatorFeeReceiver(address _operator, address _addr) external view returns (bool);\n\n function operatorURI() external view returns (string memory);\n}\n",
+ "src/interfaces/ITokenSeller.sol": "// SPDX-License-Identifier: CC0-1.0\n\npragma solidity 0.8.15;\n\ninterface ITokenSeller {\n /**\n * @dev Sells tokens for ETH.\n * Prior to calling this function, contract balance of token0 should be greater than or equal to the sold amount.\n * @param _receiver native ETH receiver.\n * @param _amount amount of tokens to sell.\n * @return (received eth amount, refunded token amount).\n */\n function sellForETH(address _receiver, uint256 _amount) external returns (uint256, uint256);\n\n /**\n * @dev Estimates amount of received ETH, when selling given amount of tokens via sellForETH function.\n * @param _amount amount of tokens to sell.\n * @return received eth amount.\n */\n function quoteSellForETH(uint256 _amount) external returns (uint256);\n}\n",
+ "src/interfaces/ITransferVerifier.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.15;\n\ninterface ITransferVerifier {\n function verifyProof(uint256[5] memory input, uint256[8] memory p) external view returns (bool);\n}\n",
+ "src/interfaces/ITreeVerifier.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.15;\n\ninterface ITreeVerifier {\n function verifyProof(uint256[3] memory input, uint256[8] memory p) external view returns (bool);\n}\n",
+ "src/interfaces/IZkBobDirectDepositQueue.sol": "// SPDX-License-Identifier: CC0-1.0\n\npragma solidity ^0.8.0;\n\ninterface IZkBobDirectDepositQueue {\n function collect(\n uint256[] calldata _indices,\n uint256 _out_commit\n )\n external\n returns (uint256 total, uint256 totalFee, uint256 hashsum, bytes memory message);\n}\n",
+ "src/interfaces/IZkBobPool.sol": "// SPDX-License-Identifier: CC0-1.0\n\npragma solidity ^0.8.0;\n\ninterface IZkBobPool {\n function pool_id() external view returns (uint256);\n\n function recordDirectDeposit(address _sender, uint256 _amount) external;\n}\n",
+ "src/proxy/EIP1967Admin.sol": "// SPDX-License-Identifier: CC0-1.0\n\npragma solidity 0.8.15;\n\n/**\n * @title EIP1967Admin\n * @dev Upgradeable proxy pattern implementation according to minimalistic EIP1967.\n */\ncontract EIP1967Admin {\n // EIP 1967\n // bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)\n uint256 internal constant EIP1967_ADMIN_STORAGE = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;\n\n modifier onlyAdmin() {\n require(msg.sender == _admin(), \"EIP1967Admin: not an admin\");\n _;\n }\n\n function _admin() internal view returns (address res) {\n assembly {\n res := sload(EIP1967_ADMIN_STORAGE)\n }\n }\n}\n",
+ "src/utils/Ownable.sol": "// SPDX-License-Identifier: CC0-1.0\n\npragma solidity 0.8.15;\n\nimport \"@openzeppelin/contracts/access/Ownable.sol\" as OZOwnable;\n\n/**\n * @title Ownable\n */\ncontract Ownable is OZOwnable.Ownable {\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view override {\n require(_isOwner(), \"Ownable: caller is not the owner\");\n }\n\n /**\n * @dev Tells if caller is the contract owner.\n * @return true, if caller is the contract owner.\n */\n function _isOwner() internal view virtual returns (bool) {\n return owner() == _msgSender();\n }\n}\n",
+ "src/zkbob/ZkBobPool.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.15;\n\nimport \"@openzeppelin/contracts/utils/Address.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {SafeERC20} from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport \"@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol\";\nimport \"@uniswap/v3-periphery/contracts/interfaces/IPeripheryImmutableState.sol\";\nimport \"@uniswap/v3-periphery/contracts/interfaces/external/IWETH9.sol\";\nimport \"../interfaces/ITransferVerifier.sol\";\nimport \"../interfaces/ITreeVerifier.sol\";\nimport \"../interfaces/IBatchDepositVerifier.sol\";\nimport \"../interfaces/IMintableERC20.sol\";\nimport \"../interfaces/IOperatorManager.sol\";\nimport \"../interfaces/IERC20Permit.sol\";\nimport \"../interfaces/ITokenSeller.sol\";\nimport \"../interfaces/IZkBobDirectDepositQueue.sol\";\nimport \"../interfaces/IZkBobPool.sol\";\nimport \"./utils/Parameters.sol\";\nimport \"./utils/ZkBobAccounting.sol\";\nimport \"../utils/Ownable.sol\";\nimport \"../proxy/EIP1967Admin.sol\";\n\n/**\n * @title ZkBobPool\n * Shielded transactions pool for BOB tokens.\n */\ncontract ZkBobPool is IZkBobPool, EIP1967Admin, Ownable, Parameters, ZkBobAccounting {\n using SafeERC20 for IERC20;\n\n uint256 internal constant MAX_POOL_ID = 0xffffff;\n uint256 internal constant TOKEN_DENOMINATOR = 1_000_000_000;\n bytes4 internal constant MESSAGE_PREFIX_COMMON_V1 = 0x00000000;\n// bytes4 internal constant MESSAGE_PREFIX_DIRECT_DEPOSIT_V1 = 0x00000001;\n// uint256 internal constant MAX_NUMBER_OF_DIRECT_DEPOSITS = 16;\n\n uint256 public immutable pool_id;\n ITransferVerifier public immutable transfer_verifier;\n ITreeVerifier public immutable tree_verifier;\n IBatchDepositVerifier public immutable batch_deposit_verifier;\n address public immutable token;\n IZkBobDirectDepositQueue public immutable direct_deposit_queue;\n\n IOperatorManager public operatorManager;\n\n mapping(uint256 => uint256) public nullifiers;\n mapping(uint256 => uint256) public roots;\n bytes32 public all_messages_hash;\n\n mapping(address => uint256) public accumulatedFee;\n\n ITokenSeller public tokenSeller;\n\n event UpdateTokenSeller(address seller);\n event UpdateOperatorManager(address manager);\n event WithdrawFee(address indexed operator, uint256 fee);\n\n event Message(uint256 indexed index, bytes32 indexed hash, bytes message);\n\n constructor(\n uint256 __pool_id,\n address _token,\n ITransferVerifier _transfer_verifier,\n ITreeVerifier _tree_verifier,\n IBatchDepositVerifier _batch_deposit_verifier,\n address _direct_deposit_queue\n ) {\n require(__pool_id <= MAX_POOL_ID, \"ZkBobPool: exceeds max pool id\");\n require(Address.isContract(_token), \"ZkBobPool: not a contract\");\n require(Address.isContract(address(_transfer_verifier)), \"ZkBobPool: not a contract\");\n require(Address.isContract(address(_tree_verifier)), \"ZkBobPool: not a contract\");\n require(Address.isContract(_direct_deposit_queue), \"ZkBobPool: not a contract\");\n pool_id = __pool_id;\n token = _token;\n transfer_verifier = _transfer_verifier;\n tree_verifier = _tree_verifier;\n batch_deposit_verifier = _batch_deposit_verifier;\n direct_deposit_queue = IZkBobDirectDepositQueue(_direct_deposit_queue);\n }\n\n /**\n * @dev Throws if called by any account other than the current relayer operator.\n */\n modifier onlyOperator() {\n require(operatorManager.isOperator(_msgSender()), \"ZkBobPool: not an operator\");\n _;\n }\n\n /**\n * @dev Initializes pool proxy storage.\n * Callable only once and only through EIP1967Proxy constructor / upgradeToAndCall.\n * @param _root initial empty merkle tree root.\n * @param _tvlCap initial upper cap on the entire pool tvl, 18 decimals.\n * @param _dailyDepositCap initial daily limit on the sum of all deposits, 18 decimals.\n * @param _dailyWithdrawalCap initial daily limit on the sum of all withdrawals, 18 decimals.\n * @param _dailyUserDepositCap initial daily limit on the sum of all per-address deposits, 18 decimals.\n * @param _depositCap initial limit on the amount of a single deposit, 18 decimals.\n * @param _dailyUserDirectDepositCap initial daily limit on the sum of all per-address direct deposits, 18 decimals.\n * @param _directDepositCap initial limit on the amount of a single direct deposit, 18 decimals.\n */\n function initialize(\n uint256 _root,\n uint256 _tvlCap,\n uint256 _dailyDepositCap,\n uint256 _dailyWithdrawalCap,\n uint256 _dailyUserDepositCap,\n uint256 _depositCap,\n uint256 _dailyUserDirectDepositCap,\n uint256 _directDepositCap\n )\n external\n {\n require(msg.sender == address(this), \"ZkBobPool: not initializer\");\n require(roots[0] == 0, \"ZkBobPool: already initialized\");\n require(_root != 0, \"ZkBobPool: zero root\");\n roots[0] = _root;\n _setLimits(\n 0,\n _tvlCap / TOKEN_DENOMINATOR,\n _dailyDepositCap / TOKEN_DENOMINATOR,\n _dailyWithdrawalCap / TOKEN_DENOMINATOR,\n _dailyUserDepositCap / TOKEN_DENOMINATOR,\n _depositCap / TOKEN_DENOMINATOR,\n _dailyUserDirectDepositCap / TOKEN_DENOMINATOR,\n _directDepositCap / TOKEN_DENOMINATOR\n );\n }\n\n /**\n * @dev Updates token seller contract used for native coin withdrawals.\n * Callable only by the contract owner / proxy admin.\n * @param _seller new token seller contract implementation. address(0) will deactivate native withdrawals.\n */\n function setTokenSeller(address _seller) external onlyOwner {\n tokenSeller = ITokenSeller(_seller);\n emit UpdateTokenSeller(_seller);\n }\n\n /**\n * @dev Updates used operator manager contract.\n * Callable only by the contract owner / proxy admin.\n * @param _operatorManager new operator manager implementation.\n */\n function setOperatorManager(IOperatorManager _operatorManager) external onlyOwner {\n require(address(_operatorManager) != address(0), \"ZkBobPool: manager is zero address\");\n operatorManager = _operatorManager;\n emit UpdateOperatorManager(address(_operatorManager));\n }\n\n /**\n * @dev Tells the denominator for converting BOB into zkBOB units.\n * 1e18 BOB units = 1e9 zkBOB units.\n */\n function denominator() external pure returns (uint256) {\n return TOKEN_DENOMINATOR;\n }\n\n /**\n * @dev Tells the current merkle tree index, which will be used for the next operation.\n * Each operation increases merkle tree size by 128, so index is equal to the total number of seen operations, multiplied by 128.\n * @return next operator merkle index.\n */\n function pool_index() external view returns (uint256) {\n return _txCount() << 7;\n }\n\n function _root() internal view override returns (uint256) {\n return roots[_transfer_index()];\n }\n\n function _pool_id() internal view override returns (uint256) {\n return pool_id;\n }\n\n /**\n * @dev Perform a zkBob pool transaction.\n * Callable only by the current operator.\n * Method uses a custom ABI encoding scheme described in CustomABIDecoder.\n * Single transact() call performs either deposit, withdrawal or shielded transfer operation.\n */\n function transact() external onlyOperator {\n address user;\n uint256 txType = _tx_type();\n if (txType == 0) {\n user = _deposit_spender();\n } else if (txType == 2) {\n user = _memo_receiver();\n } else if (txType == 3) {\n user = _memo_permit_holder();\n }\n int256 transfer_token_delta = _transfer_token_amount();\n (,, uint256 txCount) = _recordOperation(user, transfer_token_delta);\n\n uint256 nullifier = _transfer_nullifier();\n {\n uint256 _pool_index = txCount << 7;\n\n require(nullifiers[nullifier] == 0, \"ZkBobPool: doublespend detected\");\n require(_transfer_index() <= _pool_index, \"ZkBobPool: transfer index out of bounds\");\n require(transfer_verifier.verifyProof(_transfer_pub(), _transfer_proof()), \"ZkBobPool: bad transfer proof\");\n require(\n tree_verifier.verifyProof(_tree_pub(roots[_pool_index]), _tree_proof()), \"ZkBobPool: bad tree proof\"\n );\n\n nullifiers[nullifier] = uint256(keccak256(abi.encodePacked(_transfer_out_commit(), _transfer_delta())));\n _pool_index += 128;\n roots[_pool_index] = _tree_root_after();\n bytes memory message = _memo_message();\n // restrict memo message prefix (items count in little endian) to be < 2**16\n require(bytes4(message) & 0x0000ffff == MESSAGE_PREFIX_COMMON_V1, \"ZkBobPool: bad message prefix\");\n bytes32 message_hash = keccak256(message);\n bytes32 _all_messages_hash = keccak256(abi.encodePacked(all_messages_hash, message_hash));\n all_messages_hash = _all_messages_hash;\n emit Message(_pool_index, _all_messages_hash, message);\n }\n\n uint256 fee = _memo_fee();\n int256 token_amount = transfer_token_delta + int256(fee);\n int256 energy_amount = _transfer_energy_amount();\n\n if (txType == 0) {\n // Deposit\n require(transfer_token_delta > 0 && energy_amount == 0, \"ZkBobPool: incorrect deposit amounts\");\n IERC20(token).safeTransferFrom(user, address(this), uint256(token_amount) * TOKEN_DENOMINATOR);\n } else if (txType == 1) {\n // Transfer\n require(token_amount == 0 && energy_amount == 0, \"ZkBobPool: incorrect transfer amounts\");\n } else if (txType == 2) {\n // Withdraw\n require(token_amount <= 0 && energy_amount <= 0, \"ZkBobPool: incorrect withdraw amounts\");\n\n uint256 native_amount = _memo_native_amount() * TOKEN_DENOMINATOR;\n uint256 withdraw_amount = uint256(-token_amount) * TOKEN_DENOMINATOR;\n\n if (native_amount > 0) {\n ITokenSeller seller = tokenSeller;\n if (address(seller) != address(0)) {\n IERC20(token).safeTransfer(address(seller), native_amount);\n (, uint256 refunded) = seller.sellForETH(user, native_amount);\n withdraw_amount = withdraw_amount - native_amount + refunded;\n }\n }\n\n if (withdraw_amount > 0) {\n IERC20(token).safeTransfer(user, withdraw_amount);\n }\n\n // energy withdrawals are not yet implemented, any transaction with non-zero energy_amount will revert\n // future version of the protocol will support energy withdrawals through negative energy_amount\n if (energy_amount < 0) {\n revert(\"ZkBobPool: XP claiming is not yet enabled\");\n }\n } else if (txType == 3) {\n // Permittable token deposit\n require(transfer_token_delta > 0 && energy_amount == 0, \"ZkBobPool: incorrect deposit amounts\");\n (uint8 v, bytes32 r, bytes32 s) = _permittable_deposit_signature();\n IERC20Permit(token).receiveWithSaltedPermit(\n user, uint256(token_amount) * TOKEN_DENOMINATOR, _memo_permit_deadline(), bytes32(nullifier), v, r, s\n );\n } else {\n revert(\"ZkBobPool: Incorrect transaction type\");\n }\n\n if (fee > 0) {\n accumulatedFee[msg.sender] += fee;\n }\n }\n\n function appendDirectDeposits(\n uint256 _root_after,\n uint256[] calldata _indices,\n uint256 _out_commit,\n uint256[8] memory _batch_deposit_proof,\n uint256[8] memory _tree_proof\n )\n external\n onlyOperator\n {\n (uint256 total, uint256 totalFee, uint256 hashsum, bytes memory message) =\n direct_deposit_queue.collect(_indices, _out_commit);\n\n uint256 txCount = _processDirectDepositBatch(total);\n uint256 _pool_index = txCount << 7;\n\n // verify that _out_commit corresponds to zero output account + 16 chosen notes + 111 empty notes\n require(\n batch_deposit_verifier.verifyProof([hashsum], _batch_deposit_proof), \"ZkBobPool: bad batch deposit proof\"\n );\n\n uint256[3] memory tree_pub = [roots[_pool_index], _root_after, _out_commit];\n require(tree_verifier.verifyProof(tree_pub, _tree_proof), \"ZkBobPool: bad tree proof\");\n\n _pool_index += 128;\n roots[_pool_index] = _root_after;\n bytes32 message_hash = keccak256(message);\n bytes32 _all_messages_hash = keccak256(abi.encodePacked(all_messages_hash, message_hash));\n all_messages_hash = _all_messages_hash;\n\n if (totalFee > 0) {\n accumulatedFee[msg.sender] += totalFee;\n }\n\n emit Message(_pool_index, _all_messages_hash, message);\n }\n\n function recordDirectDeposit(address _sender, uint256 _amount) external {\n require(msg.sender == address(direct_deposit_queue),\"ZkBobPool: not authorized\");\n _checkDirectDepositLimits(_sender, _amount);\n }\n\n /**\n * @dev Withdraws accumulated fee on behalf of an operator.\n * Callable only by the operator itself, or by a pre-configured operator fee receiver address.\n * @param _operator address of an operator account to withdraw fee from.\n * @param _to address of the accumulated fee tokens receiver.\n */\n function withdrawFee(address _operator, address _to) external {\n require(\n _operator == msg.sender || operatorManager.isOperatorFeeReceiver(_operator, msg.sender),\n \"ZkBobPool: not authorized\"\n );\n uint256 fee = accumulatedFee[_operator] * TOKEN_DENOMINATOR;\n require(fee > 0, \"ZkBobPool: no fee to withdraw\");\n IERC20(token).safeTransfer(_to, fee);\n accumulatedFee[_operator] = 0;\n emit WithdrawFee(_operator, fee);\n }\n\n /**\n * @dev Updates pool usage limits.\n * Callable only by the contract owner / proxy admin.\n * @param _tier pool limits tier (0-254).\n * @param _tvlCap new upper cap on the entire pool tvl, 18 decimals.\n * @param _dailyDepositCap new daily limit on the sum of all deposits, 18 decimals.\n * @param _dailyWithdrawalCap new daily limit on the sum of all withdrawals, 18 decimals.\n * @param _dailyUserDepositCap new daily limit on the sum of all per-address deposits, 18 decimals.\n * @param _depositCap new limit on the amount of a single deposit, 18 decimals.\n * @param _dailyUserDirectDepositCap new daily limit on the sum of all per-address direct deposits, 18 decimals.\n * @param _directDepositCap new limit on the amount of a single direct deposit, 18 decimals.\n */\n function setLimits(\n uint8 _tier,\n uint256 _tvlCap,\n uint256 _dailyDepositCap,\n uint256 _dailyWithdrawalCap,\n uint256 _dailyUserDepositCap,\n uint256 _depositCap,\n uint256 _dailyUserDirectDepositCap,\n uint256 _directDepositCap\n )\n external\n onlyOwner\n {\n _setLimits(\n _tier,\n _tvlCap / TOKEN_DENOMINATOR,\n _dailyDepositCap / TOKEN_DENOMINATOR,\n _dailyWithdrawalCap / TOKEN_DENOMINATOR,\n _dailyUserDepositCap / TOKEN_DENOMINATOR,\n _depositCap / TOKEN_DENOMINATOR,\n _dailyUserDirectDepositCap / TOKEN_DENOMINATOR,\n _directDepositCap / TOKEN_DENOMINATOR\n );\n }\n\n /**\n * @dev Resets daily limit usage for the current day.\n * Callable only by the contract owner / proxy admin.\n * @param _tier tier id to reset daily limits for.\n */\n function resetDailyLimits(uint8 _tier) external onlyOwner {\n _resetDailyLimits(_tier);\n }\n\n /**\n * @dev Updates users limit tiers.\n * Callable only by the contract owner / proxy admin.\n * @param _tier pool limits tier (0-255).\n * 0 is the default tier.\n * 1-254 are custom pool limit tiers, configured at runtime.\n * 255 is the special tier with zero limits, used to effectively prevent some address from accessing the pool.\n * @param _users list of user account addresses to assign a tier for.\n */\n function setUsersTier(uint8 _tier, address[] memory _users) external onlyOwner {\n _setUsersTier(_tier, _users);\n }\n\n /**\n * @dev Tells if caller is the contract owner.\n * Gives ownership rights to the proxy admin as well.\n * @return true, if caller is the contract owner or proxy admin.\n */\n function _isOwner() internal view override returns (bool) {\n return super._isOwner() || _admin() == _msgSender();\n }\n}\n",
+ "src/zkbob/utils/CustomABIDecoder.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.15;\n\ncontract CustomABIDecoder {\n uint256 constant transfer_nullifier_pos = 4;\n uint256 constant transfer_nullifier_size = 32;\n uint256 constant uint256_size = 32;\n\n function _loaduint256(uint256 pos) internal pure returns (uint256 r) {\n assembly {\n r := calldataload(pos)\n }\n }\n\n function _transfer_nullifier() internal pure returns (uint256 r) {\n r = _loaduint256(transfer_nullifier_pos);\n }\n\n uint256 constant transfer_out_commit_pos = transfer_nullifier_pos + transfer_nullifier_size;\n uint256 constant transfer_out_commit_size = 32;\n\n function _transfer_out_commit() internal pure returns (uint256 r) {\n r = _loaduint256(transfer_out_commit_pos);\n }\n\n uint256 constant transfer_index_pos = transfer_out_commit_pos + transfer_out_commit_size;\n uint256 constant transfer_index_size = 6;\n\n function _transfer_index() internal pure returns (uint48 r) {\n r = uint48(_loaduint256(transfer_index_pos + transfer_index_size - uint256_size));\n }\n\n uint256 constant transfer_energy_amount_pos = transfer_index_pos + transfer_index_size;\n uint256 constant transfer_energy_amount_size = 14;\n\n function _transfer_energy_amount() internal pure returns (int112 r) {\n r = int112(uint112(_loaduint256(transfer_energy_amount_pos + transfer_energy_amount_size - uint256_size)));\n }\n\n uint256 constant transfer_token_amount_pos = transfer_energy_amount_pos + transfer_energy_amount_size;\n uint256 constant transfer_token_amount_size = 8;\n\n function _transfer_token_amount() internal pure returns (int64 r) {\n r = int64(uint64(_loaduint256(transfer_token_amount_pos + transfer_token_amount_size - uint256_size)));\n }\n\n uint256 constant transfer_proof_pos = transfer_token_amount_pos + transfer_token_amount_size;\n uint256 constant transfer_proof_size = 256;\n\n function _transfer_proof() internal pure returns (uint256[8] calldata r) {\n uint256 pos = transfer_proof_pos;\n assembly {\n r := pos\n }\n }\n\n uint256 constant tree_root_after_pos = transfer_proof_pos + transfer_proof_size;\n uint256 constant tree_root_after_size = 32;\n\n function _tree_root_after() internal pure returns (uint256 r) {\n r = _loaduint256(tree_root_after_pos);\n }\n\n uint256 constant tree_proof_pos = tree_root_after_pos + tree_root_after_size;\n uint256 constant tree_proof_size = 256;\n\n function _tree_proof() internal pure returns (uint256[8] calldata r) {\n uint256 pos = tree_proof_pos;\n assembly {\n r := pos\n }\n }\n\n uint256 constant tx_type_pos = tree_proof_pos + tree_proof_size;\n uint256 constant tx_type_size = 2;\n uint256 constant tx_type_mask = (1 << (tx_type_size * 8)) - 1;\n\n function _tx_type() internal pure returns (uint256 r) {\n r = _loaduint256(tx_type_pos + tx_type_size - uint256_size) & tx_type_mask;\n }\n\n uint256 constant memo_data_size_pos = tx_type_pos + tx_type_size;\n uint256 constant memo_data_size_size = 2;\n uint256 constant memo_data_size_mask = (1 << (memo_data_size_size * 8)) - 1;\n\n uint256 constant memo_data_pos = memo_data_size_pos + memo_data_size_size;\n\n function _memo_data_size() internal pure returns (uint256 r) {\n r = _loaduint256(memo_data_size_pos + memo_data_size_size - uint256_size) & memo_data_size_mask;\n }\n\n function _memo_data() internal pure returns (bytes calldata r) {\n uint256 offset = memo_data_pos;\n uint256 length = _memo_data_size();\n assembly {\n r.offset := offset\n r.length := length\n }\n }\n\n function _sign_r_vs_pos() internal pure returns (uint256) {\n return memo_data_pos + _memo_data_size();\n }\n\n uint256 constant sign_r_vs_size = 64;\n\n function _sign_r_vs() internal pure returns (bytes32 r, bytes32 vs) {\n uint256 offset = _sign_r_vs_pos();\n assembly {\n r := calldataload(offset)\n vs := calldataload(add(offset, 32))\n }\n }\n\n uint256 constant transfer_delta_size =\n transfer_index_size + transfer_energy_amount_size + transfer_token_amount_size;\n uint256 constant transfer_delta_mask = (1 << (transfer_delta_size * 8)) - 1;\n\n function _transfer_delta() internal pure returns (uint256 r) {\n r = _loaduint256(transfer_index_pos + transfer_delta_size - uint256_size) & transfer_delta_mask;\n }\n\n function _memo_fixed_size() internal pure returns (uint256 r) {\n uint256 t = _tx_type();\n if (t == 0 || t == 1) {\n // fee\n // 8\n r = 8;\n } else if (t == 2) {\n // fee + native amount + recipient\n // 8 + 8 + 20\n r = 36;\n } else if (t == 3) {\n // fee + deadline + address\n // 8 + 8 + 20\n r = 36;\n } else {\n revert();\n }\n }\n\n function _memo_message() internal pure returns (bytes calldata r) {\n uint256 memo_fixed_size = _memo_fixed_size();\n uint256 offset = memo_data_pos + memo_fixed_size;\n uint256 length = _memo_data_size() - memo_fixed_size;\n assembly {\n r.offset := offset\n r.length := length\n }\n }\n\n uint256 constant memo_fee_pos = memo_data_pos;\n uint256 constant memo_fee_size = 8;\n uint256 constant memo_fee_mask = (1 << (memo_fee_size * 8)) - 1;\n\n function _memo_fee() internal pure returns (uint256 r) {\n r = _loaduint256(memo_fee_pos + memo_fee_size - uint256_size) & memo_fee_mask;\n }\n\n // Withdraw specific data\n\n uint256 constant memo_native_amount_pos = memo_fee_pos + memo_fee_size;\n uint256 constant memo_native_amount_size = 8;\n uint256 constant memo_native_amount_mask = (1 << (memo_native_amount_size * 8)) - 1;\n\n function _memo_native_amount() internal pure returns (uint256 r) {\n r = _loaduint256(memo_native_amount_pos + memo_native_amount_size - uint256_size) & memo_native_amount_mask;\n }\n\n uint256 constant memo_receiver_pos = memo_native_amount_pos + memo_native_amount_size;\n uint256 constant memo_receiver_size = 20;\n\n function _memo_receiver() internal pure returns (address r) {\n r = address(uint160(_loaduint256(memo_receiver_pos + memo_receiver_size - uint256_size)));\n }\n\n // Permittable token deposit specific data\n\n uint256 constant memo_permit_deadline_pos = memo_fee_pos + memo_fee_size;\n uint256 constant memo_permit_deadline_size = 8;\n\n function _memo_permit_deadline() internal pure returns (uint64 r) {\n r = uint64(_loaduint256(memo_permit_deadline_pos + memo_permit_deadline_size - uint256_size));\n }\n\n uint256 constant memo_permit_holder_pos = memo_permit_deadline_pos + memo_permit_deadline_size;\n uint256 constant memo_permit_holder_size = 20;\n\n function _memo_permit_holder() internal pure returns (address r) {\n r = address(uint160(_loaduint256(memo_permit_holder_pos + memo_permit_holder_size - uint256_size)));\n }\n}\n",
+ "src/zkbob/utils/Parameters.sol": "// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.15;\n\nimport \"@openzeppelin/contracts/utils/cryptography/ECDSA.sol\";\nimport \"./CustomABIDecoder.sol\";\n\nabstract contract Parameters is CustomABIDecoder {\n uint256 constant R = 21888242871839275222246405745257275088548364400416034343698204186575808495617;\n bytes32 constant S_MASK = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;\n\n function _root() internal view virtual returns (uint256);\n function _pool_id() internal view virtual returns (uint256);\n\n function _transfer_pub() internal view returns (uint256[5] memory r) {\n r[0] = _root();\n r[1] = _transfer_nullifier();\n r[2] = _transfer_out_commit();\n r[3] = _transfer_delta() + (_pool_id() << (transfer_delta_size * 8));\n r[4] = uint256(keccak256(_memo_data())) % R;\n }\n\n function _tree_pub(uint256 _root_before) internal view returns (uint256[3] memory r) {\n r[0] = _root_before;\n r[1] = _tree_root_after();\n r[2] = _transfer_out_commit();\n }\n\n // NOTE only valid in the context of normal deposit (tx_type=0)\n function _deposit_spender() internal pure returns (address) {\n (bytes32 r, bytes32 vs) = _sign_r_vs();\n return ECDSA.recover(ECDSA.toEthSignedMessageHash(bytes32(_transfer_nullifier())), r, vs);\n }\n\n // NOTE only valid in the context of permittable token deposit (tx_type=3)\n function _permittable_deposit_signature() internal pure returns (uint8, bytes32, bytes32) {\n (bytes32 r, bytes32 vs) = _sign_r_vs();\n return (uint8((uint256(vs) >> 255) + 27), r, vs & S_MASK);\n }\n}\n",
+ "src/zkbob/utils/ZkBobAccounting.sol": "// SPDX-License-Identifier: CC0-1.0\n\npragma solidity 0.8.15;\n\n/**\n * @title ZkBobAccounting\n * @dev On chain accounting for zkBob operations, limits and stats.\n * Units: 1 BOB = 1e18 wei = 1e9 zkBOB units\n * Limitations: Contract will only work correctly as long as pool tvl does not exceed 4.7e12 BOB (4.7 trillion)\n * and overall transaction count does not exceed 4.3e9 (4.3 billion). Pool usage limits cannot exceed 4.3e9 BOB (4.3 billion) per day.\n */\ncontract ZkBobAccounting {\n uint256 internal constant PRECISION = 1_000_000_000;\n uint256 internal constant SLOT_DURATION = 1 hours;\n uint256 internal constant DAY_SLOTS = 1 days / SLOT_DURATION;\n uint256 internal constant WEEK_SLOTS = 1 weeks / SLOT_DURATION;\n\n struct Slot0 {\n // max seen average tvl over period of at least 1 week (granularity of 1e9), might not be precise\n // max possible tvl - type(uint56).max * 1e9 zkBOB units ~= 7.2e16 BOB\n uint56 maxWeeklyAvgTvl;\n // max number of pool interactions over 1 week, might not be precise\n // max possible tx count - type(uint32).max ~= 4.3e9 transactions\n uint32 maxWeeklyTxCount;\n // 1 week behind snapshot time slot (granularity of 1 hour)\n // max possible timestamp - Dec 08 3883\n uint24 tailSlot;\n // active snapshot time slot (granularity of 1 hour)\n // max possible timestamp - Dec 08 3883\n uint24 headSlot;\n // cumulative sum of tvl over txCount interactions (granularity of 1e9)\n // max possible cumulative tvl ~= type(uint32).max * type(uint56).max = 4.3e9 transactions * 7.2e16 BOB\n uint88 cumTvl;\n // number of successful pool interactions since launch\n // max possible tx count - type(uint32).max ~= 4.3e9 transactions\n uint32 txCount;\n }\n\n struct Slot1 {\n // current pool tvl (granularity of 1)\n // max possible tvl - type(uint72).max * 1 zkBOB units ~= 4.7e21 zkBOB units ~= 4.7e12 BOB\n uint72 tvl;\n }\n\n struct Tier {\n TierLimits limits;\n TierStats stats;\n }\n\n struct TierLimits {\n // max cap on the entire pool tvl (granularity of 1e9)\n // max possible cap - type(uint56).max * 1e9 zkBOB units ~= 7.2e16 BOB\n uint56 tvlCap;\n // max cap on the daily deposits sum (granularity of 1e9)\n // max possible cap - type(uint32).max * 1e9 zkBOB units ~= 4.3e9 BOB\n uint32 dailyDepositCap;\n // max cap on the daily withdrawal sum (granularity of 1e9)\n // max possible cap - type(uint32).max * 1e9 zkBOB units ~= 4.3e9 BOB\n uint32 dailyWithdrawalCap;\n // max cap on the daily deposits sum for single user (granularity of 1e9)\n // max possible cap - type(uint32).max * 1e9 zkBOB units ~= 4.3e9 BOB\n uint32 dailyUserDepositCap;\n // max cap on a single deposit (granularity of 1e9)\n // max possible cap - type(uint32).max * 1e9 zkBOB units ~= 4.3e9 BOB\n uint32 depositCap;\n // max cap on a single direct deposit (granularity of 1e9)\n // max possible cap - type(uint32).max * 1e9 zkBOB units ~= 4.3e9 BOB\n uint32 directDepositCap;\n // max cap on the daily direct deposits sum for single user (granularity of 1e9)\n // max possible cap - type(uint32).max * 1e9 zkBOB units ~= 4.3e9 BOB\n uint32 dailyUserDirectDepositCap;\n }\n\n struct TierStats {\n uint16 day; // last update day number\n uint72 dailyDeposit; // sum of all deposits during given day\n uint72 dailyWithdrawal; // sum of all withdrawals during given day\n }\n\n struct Snapshot {\n uint24 nextSlot; // next slot to from the queue\n uint32 txCount; // number of successful pool interactions since launch at the time of the snapshot\n uint88 cumTvl; // cumulative sum of tvl over txCount interactions (granularity of 1e9)\n }\n\n struct UserStats {\n uint16 day; // last update day number\n uint72 dailyDeposit; // sum of user deposits during given day\n uint8 tier; // user limits tier, 0 being the default tier\n uint72 dailyDirectDeposit; // sum of user direct deposits during given day\n }\n\n struct Limits {\n uint256 tvlCap;\n uint256 tvl;\n uint256 dailyDepositCap;\n uint256 dailyDepositCapUsage;\n uint256 dailyWithdrawalCap;\n uint256 dailyWithdrawalCapUsage;\n uint256 dailyUserDepositCap;\n uint256 dailyUserDepositCapUsage;\n uint256 depositCap;\n uint8 tier;\n uint256 dailyUserDirectDepositCap;\n uint256 dailyUserDirectDepositCapUsage;\n uint256 directDepositCap;\n }\n\n Slot0 private slot0;\n Slot1 private slot1;\n mapping(uint256 => Tier) private tiers; // pool limits and usage per tier\n mapping(uint256 => Snapshot) private snapshots; // single linked list of hourly snapshots\n mapping(address => UserStats) private userStats;\n\n event UpdateLimits(uint8 indexed tier, TierLimits limits);\n event UpdateTier(address user, uint8 tier);\n\n /**\n * @dev Returns currently configured limits and remaining quotas for the given user as of the current block.\n * @param _user user for which to retrieve limits.\n * @return limits (denominated in zkBOB units = 1e-9 BOB)\n */\n function getLimitsFor(address _user) external view returns (Limits memory) {\n Slot1 memory s1 = slot1;\n UserStats memory us = userStats[_user];\n Tier storage t = tiers[uint256(us.tier)];\n TierLimits memory tl = t.limits;\n TierStats memory ts = t.stats;\n uint24 curSlot = uint24(block.timestamp / SLOT_DURATION);\n uint24 today = curSlot / uint24(DAY_SLOTS);\n return Limits({\n tvlCap: tl.tvlCap * PRECISION,\n tvl: s1.tvl,\n dailyDepositCap: tl.dailyDepositCap * PRECISION,\n dailyDepositCapUsage: (ts.day == today) ? ts.dailyDeposit : 0,\n dailyWithdrawalCap: tl.dailyWithdrawalCap * PRECISION,\n dailyWithdrawalCapUsage: (ts.day == today) ? ts.dailyWithdrawal : 0,\n dailyUserDepositCap: tl.dailyUserDepositCap * PRECISION,\n dailyUserDepositCapUsage: (us.day == today) ? us.dailyDeposit : 0,\n depositCap: tl.depositCap * PRECISION,\n tier: us.tier,\n dailyUserDirectDepositCap: tl.dailyUserDirectDepositCap * PRECISION,\n dailyUserDirectDepositCapUsage: (us.day == today) ? us.dailyDirectDeposit : 0,\n directDepositCap: tl.directDepositCap * PRECISION\n });\n }\n\n function _recordOperation(\n address _user,\n int256 _txAmount\n )\n internal\n returns (uint56 maxWeeklyAvgTvl, uint32 maxWeeklyTxCount, uint256 txCount)\n {\n Slot0 memory s0 = slot0;\n Slot1 memory s1 = slot1;\n uint24 curSlot = uint24(block.timestamp / SLOT_DURATION);\n txCount = uint256(s0.txCount);\n\n // for full correctness, next line should use \"while\" instead of \"if\"\n // however, in order to keep constant gas usage, \"if\" is being used\n // this can lead to a longer sliding window (> 1 week) in some cases,\n // but eventually it will converge back to the 1 week target\n if (s0.txCount > 0 && curSlot - s0.tailSlot > WEEK_SLOTS) {\n // if tail is more than 1 week behind, we move tail pointer to the next snapshot\n Snapshot memory sn = snapshots[s0.tailSlot];\n delete snapshots[s0.tailSlot];\n s0.tailSlot = sn.nextSlot;\n uint32 weeklyTxCount = s0.txCount - sn.txCount;\n if (weeklyTxCount > s0.maxWeeklyTxCount) {\n s0.maxWeeklyTxCount = weeklyTxCount;\n }\n uint56 avgTvl = uint56((s0.cumTvl - sn.cumTvl) / weeklyTxCount);\n if (avgTvl > s0.maxWeeklyAvgTvl) {\n s0.maxWeeklyAvgTvl = avgTvl;\n }\n }\n\n if (s0.headSlot < curSlot) {\n snapshots[s0.headSlot] = Snapshot(curSlot, s0.txCount, s0.cumTvl);\n s0.headSlot = curSlot;\n }\n\n // update head stats\n s0.cumTvl += s1.tvl / uint72(PRECISION);\n s0.txCount++;\n\n _processTVLChange(s1, _user, _txAmount);\n\n slot0 = s0;\n return (s0.maxWeeklyAvgTvl, s0.maxWeeklyTxCount, txCount);\n }\n\n function _processTVLChange(Slot1 memory s1, address _user, int256 _txAmount) internal {\n uint16 curDay = uint16(block.timestamp / SLOT_DURATION / DAY_SLOTS);\n\n if (_txAmount == 0) {\n return;\n }\n\n UserStats memory us = userStats[_user];\n Tier storage t = tiers[us.tier];\n TierLimits memory tl = t.limits;\n TierStats memory ts = t.stats;\n\n if (_txAmount > 0) {\n uint256 depositAmount = uint256(_txAmount);\n s1.tvl += uint72(depositAmount);\n\n // check all sorts of limits when processing a deposit\n require(depositAmount <= uint256(tl.depositCap) * PRECISION, \"ZkBobAccounting: single deposit cap exceeded\");\n require(uint256(s1.tvl) <= uint256(tl.tvlCap) * PRECISION, \"ZkBobAccounting: tvl cap exceeded\");\n\n if (curDay > us.day) {\n // user snapshot is outdated, day number and daily sum could be reset\n userStats[_user] =\n UserStats({day: curDay, dailyDeposit: uint72(depositAmount), tier: us.tier, dailyDirectDeposit: 0});\n } else {\n us.dailyDeposit += uint72(depositAmount);\n require(\n uint256(us.dailyDeposit) <= uint256(tl.dailyUserDepositCap) * PRECISION,\n \"ZkBobAccounting: daily user deposit cap exceeded\"\n );\n userStats[_user] = us;\n }\n\n if (curDay > ts.day) {\n // latest deposit was on an earlier day, reset daily withdrawal sum\n ts = TierStats({day: curDay, dailyDeposit: uint72(depositAmount), dailyWithdrawal: 0});\n } else {\n ts.dailyDeposit += uint72(depositAmount);\n require(\n uint256(ts.dailyDeposit) <= uint256(tl.dailyDepositCap) * PRECISION,\n \"ZkBobAccounting: daily deposit cap exceeded\"\n );\n }\n } else {\n uint256 withdrawAmount = uint256(-_txAmount);\n require(withdrawAmount <= type(uint32).max * PRECISION, \"ZkBobAccounting: withdrawal amount too large\");\n s1.tvl -= uint72(withdrawAmount);\n\n if (curDay > ts.day) {\n // latest withdrawal was on an earlier day, reset daily deposit sum\n ts = TierStats({day: curDay, dailyDeposit: 0, dailyWithdrawal: uint72(withdrawAmount)});\n } else {\n ts.dailyWithdrawal += uint72(withdrawAmount);\n require(\n uint256(ts.dailyWithdrawal) <= uint256(tl.dailyWithdrawalCap) * PRECISION,\n \"ZkBobAccounting: daily withdrawal cap exceeded\"\n );\n }\n }\n\n slot1 = s1;\n t.stats = ts;\n }\n\n function _checkDirectDepositLimits(address _user, uint256 _amount) internal {\n uint16 curDay = uint16(block.timestamp / SLOT_DURATION / DAY_SLOTS);\n\n UserStats memory us = userStats[_user];\n TierLimits memory tl = tiers[us.tier].limits;\n\n // check all sorts of limits when processing a deposit\n require(\n _amount <= uint256(tl.directDepositCap) * PRECISION, \"ZkBobAccounting: single direct deposit cap exceeded\"\n );\n\n if (curDay > us.day) {\n // user snapshot is outdated, day number and daily sum could be reset\n us = UserStats({day: curDay, dailyDeposit: 0, tier: us.tier, dailyDirectDeposit: uint72(_amount)});\n } else {\n us.dailyDirectDeposit += uint72(_amount);\n require(\n uint256(us.dailyDirectDeposit) <= uint256(tl.dailyUserDirectDepositCap) * PRECISION,\n \"ZkBobAccounting: daily user direct deposit cap exceeded\"\n );\n }\n userStats[_user] = us;\n }\n\n function _processDirectDepositBatch(uint256 _totalAmount) internal returns (uint256) {\n slot1.tvl += uint72(_totalAmount);\n return slot0.txCount++;\n }\n\n function _resetDailyLimits(uint8 _tier) internal {\n delete tiers[_tier].stats;\n }\n\n function _setLimits(\n uint8 _tier,\n uint256 _tvlCap,\n uint256 _dailyDepositCap,\n uint256 _dailyWithdrawalCap,\n uint256 _dailyUserDepositCap,\n uint256 _depositCap,\n uint256 _dailyUserDirectDepositCap,\n uint256 _directDepositCap\n )\n internal\n {\n require(_tier < 255, \"ZkBobAccounting: invalid limit tier\");\n require(_depositCap > 0, \"ZkBobAccounting: zero deposit cap\");\n require(_tvlCap <= type(uint56).max * PRECISION, \"ZkBobAccounting: tvl cap too large\");\n require(_dailyDepositCap <= type(uint32).max * PRECISION, \"ZkBobAccounting: daily deposit cap too large\");\n require(_dailyWithdrawalCap <= type(uint32).max * PRECISION, \"ZkBobAccounting: daily withdrawal cap too large\");\n require(_dailyUserDepositCap >= _depositCap, \"ZkBobAccounting: daily user deposit cap too low\");\n require(_dailyDepositCap >= _dailyUserDepositCap, \"ZkBobAccounting: daily deposit cap too low\");\n require(_tvlCap >= _dailyDepositCap, \"ZkBobAccounting: tvl cap too low\");\n require(_dailyWithdrawalCap > 0, \"ZkBobAccounting: zero daily withdrawal cap\");\n require(\n _dailyUserDirectDepositCap >= _directDepositCap, \"ZkBobAccounting: daily user direct deposit cap too low\"\n );\n TierLimits memory tl = TierLimits({\n tvlCap: uint56(_tvlCap / PRECISION),\n dailyDepositCap: uint32(_dailyDepositCap / PRECISION),\n dailyWithdrawalCap: uint32(_dailyWithdrawalCap / PRECISION),\n dailyUserDepositCap: uint32(_dailyUserDepositCap / PRECISION),\n depositCap: uint32(_depositCap / PRECISION),\n dailyUserDirectDepositCap: uint32(_dailyUserDirectDepositCap / PRECISION),\n directDepositCap: uint32(_directDepositCap / PRECISION)\n });\n tiers[_tier].limits = tl;\n emit UpdateLimits(_tier, tl);\n }\n\n function _setUsersTier(uint8 _tier, address[] memory _users) internal {\n require(\n _tier == 255 || tiers[uint256(_tier)].limits.tvlCap > 0, \"ZkBobAccounting: non-existing pool limits tier\"\n );\n for (uint256 i = 0; i < _users.length; i++) {\n address user = _users[i];\n userStats[user].tier = _tier;\n emit UpdateTier(user, _tier);\n }\n }\n\n function _txCount() internal view returns (uint256) {\n return slot0.txCount;\n }\n}\n"
+ },
+ "abi": "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"__pool_id\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_token\",\"type\":\"address\"},{\"internalType\":\"contract ITransferVerifier\",\"name\":\"_transfer_verifier\",\"type\":\"address\"},{\"internalType\":\"contract ITreeVerifier\",\"name\":\"_tree_verifier\",\"type\":\"address\"},{\"internalType\":\"contract IBatchDepositVerifier\",\"name\":\"_batch_deposit_verifier\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_direct_deposit_queue\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"hash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\"}],\"name\":\"Message\",\"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\":\"uint8\",\"name\":\"tier\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"uint56\",\"name\":\"tvlCap\",\"type\":\"uint56\"},{\"internalType\":\"uint32\",\"name\":\"dailyDepositCap\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"dailyWithdrawalCap\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"dailyUserDepositCap\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"depositCap\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"directDepositCap\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"dailyUserDirectDepositCap\",\"type\":\"uint32\"}],\"indexed\":false,\"internalType\":\"struct ZkBobAccounting.TierLimits\",\"name\":\"limits\",\"type\":\"tuple\"}],\"name\":\"UpdateLimits\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"manager\",\"type\":\"address\"}],\"name\":\"UpdateOperatorManager\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"tier\",\"type\":\"uint8\"}],\"name\":\"UpdateTier\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"seller\",\"type\":\"address\"}],\"name\":\"UpdateTokenSeller\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"}],\"name\":\"WithdrawFee\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"accumulatedFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"all_messages_hash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_root_after\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"_indices\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256\",\"name\":\"_out_commit\",\"type\":\"uint256\"},{\"internalType\":\"uint256[8]\",\"name\":\"_batch_deposit_proof\",\"type\":\"uint256[8]\"},{\"internalType\":\"uint256[8]\",\"name\":\"_tree_proof\",\"type\":\"uint256[8]\"}],\"name\":\"appendDirectDeposits\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"batch_deposit_verifier\",\"outputs\":[{\"internalType\":\"contract IBatchDepositVerifier\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"denominator\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"direct_deposit_queue\",\"outputs\":[{\"internalType\":\"contract IZkBobDirectDepositQueue\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_user\",\"type\":\"address\"}],\"name\":\"getLimitsFor\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"tvlCap\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"tvl\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"dailyDepositCap\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"dailyDepositCapUsage\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"dailyWithdrawalCap\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"dailyWithdrawalCapUsage\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"dailyUserDepositCap\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"dailyUserDepositCapUsage\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"depositCap\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"tier\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"dailyUserDirectDepositCap\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"dailyUserDirectDepositCapUsage\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"directDepositCap\",\"type\":\"uint256\"}],\"internalType\":\"struct ZkBobAccounting.Limits\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_root\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_tvlCap\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_dailyDepositCap\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_dailyWithdrawalCap\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_dailyUserDepositCap\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_depositCap\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_dailyUserDirectDepositCap\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_directDepositCap\",\"type\":\"uint256\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"nullifiers\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"operatorManager\",\"outputs\":[{\"internalType\":\"contract IOperatorManager\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pool_id\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pool_index\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"recordDirectDeposit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"_tier\",\"type\":\"uint8\"}],\"name\":\"resetDailyLimits\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"roots\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"_tier\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"_tvlCap\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_dailyDepositCap\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_dailyWithdrawalCap\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_dailyUserDepositCap\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_depositCap\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_dailyUserDirectDepositCap\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_directDepositCap\",\"type\":\"uint256\"}],\"name\":\"setLimits\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IOperatorManager\",\"name\":\"_operatorManager\",\"type\":\"address\"}],\"name\":\"setOperatorManager\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_seller\",\"type\":\"address\"}],\"name\":\"setTokenSeller\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"_tier\",\"type\":\"uint8\"},{\"internalType\":\"address[]\",\"name\":\"_users\",\"type\":\"address[]\"}],\"name\":\"setUsersTier\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tokenSeller\",\"outputs\":[{\"internalType\":\"contract ITokenSeller\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"transact\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"transfer_verifier\",\"outputs\":[{\"internalType\":\"contract ITransferVerifier\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tree_verifier\",\"outputs\":[{\"internalType\":\"contract ITreeVerifier\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_operator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_to\",\"type\":\"address\"}],\"name\":\"withdrawFee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]",
+ "constructorArguments": null,
+ "matchType": "PARTIAL",
+ "compilationArtifacts": null,
+ "creationInputArtifacts": null,
+ "deployedBytecodeArtifacts": null
+ }
+ ]
+}
\ No newline at end of file
diff --git a/apps/block_scout_web/test/test_helper.exs b/apps/block_scout_web/test/test_helper.exs
index a99ba9a234d5..b92840d3a20c 100644
--- a/apps/block_scout_web/test/test_helper.exs
+++ b/apps/block_scout_web/test/test_helper.exs
@@ -26,6 +26,10 @@ Mox.defmock(Explorer.ExchangeRates.Source.TestSource, for: Explorer.ExchangeRate
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, :manual)
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Account, :manual)
+Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonEdge, :manual)
+Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonZkevm, :manual)
+Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.RSK, :manual)
+Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Suave, :manual)
Absinthe.Test.prime(BlockScoutWeb.Schema)
diff --git a/apps/ethereum_jsonrpc/config/config.exs b/apps/ethereum_jsonrpc/config/config.exs
index e578ffbea78a..503b7a3828ed 100644
--- a/apps/ethereum_jsonrpc/config/config.exs
+++ b/apps/ethereum_jsonrpc/config/config.exs
@@ -6,7 +6,6 @@ config :ethereum_jsonrpc, EthereumJSONRPC.RequestCoordinator,
duration: :timer.minutes(1),
table: EthereumJSONRPC.RequestCoordinator.TimeoutCounter
],
- wait_per_timeout: :timer.seconds(20),
max_jitter: :timer.seconds(2)
# Add this configuration to add global RPC request throttling.
diff --git a/apps/ethereum_jsonrpc/config/runtime/test.exs b/apps/ethereum_jsonrpc/config/runtime/test.exs
index e2043f6c1435..081c952dfc9d 100644
--- a/apps/ethereum_jsonrpc/config/runtime/test.exs
+++ b/apps/ethereum_jsonrpc/config/runtime/test.exs
@@ -2,6 +2,8 @@ import Config
alias EthereumJSONRPC.Variant
+config :ethereum_jsonrpc, EthereumJSONRPC.RequestCoordinator, wait_per_timeout: 2
+
variant = Variant.get()
Code.require_file("#{variant}.exs", "#{__DIR__}/../../../explorer/config/test")
diff --git a/apps/ethereum_jsonrpc/config/test.exs b/apps/ethereum_jsonrpc/config/test.exs
index 0ed3de28b282..61e5f67398a0 100644
--- a/apps/ethereum_jsonrpc/config/test.exs
+++ b/apps/ethereum_jsonrpc/config/test.exs
@@ -6,7 +6,6 @@ config :ethereum_jsonrpc, EthereumJSONRPC.RequestCoordinator,
duration: :timer.seconds(6),
table: EthereumJSONRPC.RequestCoordinator.TimeoutCounter
],
- wait_per_timeout: 2,
max_jitter: 1,
# This should not actually limit anything in tests, but it is here to enable the relevant code for testing
throttle_rate_limit: 10_000,
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex
index 9e4fd59c2f4d..74d37fe187df 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex
@@ -39,6 +39,7 @@ defmodule EthereumJSONRPC do
Subscription,
Transport,
Utility.EndpointAvailabilityObserver,
+ Utility.RangesHelper,
Variant
}
@@ -185,24 +186,36 @@ defmodule EthereumJSONRPC do
[%{required(:block_quantity) => quantity, required(:hash_data) => data()}],
json_rpc_named_arguments
) :: {:ok, FetchedBalances.t()} | {:error, reason :: term}
- def fetch_balances(params_list, json_rpc_named_arguments)
+ def fetch_balances(params_list, json_rpc_named_arguments, chunk_size \\ nil)
when is_list(params_list) and is_list(json_rpc_named_arguments) do
filtered_params =
if Application.get_env(:ethereum_jsonrpc, :disable_archive_balances?) do
+ {:ok, max_block_number} = fetch_block_number_by_tag("latest", json_rpc_named_arguments)
+ window = Application.get_env(:ethereum_jsonrpc, :archive_balances_window)
+
params_list
|> Enum.filter(fn
%{block_quantity: "latest"} -> true
+ %{block_quantity: block_quantity} -> quantity_to_integer(block_quantity) > max_block_number - window
_ -> false
end)
else
params_list
end
- id_to_params = id_to_params(filtered_params)
+ filtered_params_in_range =
+ filtered_params
+ |> Enum.filter(fn
+ %{block_quantity: block_quantity} ->
+ block_quantity |> quantity_to_integer() |> RangesHelper.traceable_block_number?()
+ end)
+
+ id_to_params = id_to_params(filtered_params_in_range)
with {:ok, responses} <-
id_to_params
|> FetchedBalances.requests()
+ |> chunk_requests(chunk_size)
|> json_rpc(json_rpc_named_arguments) do
{:ok, FetchedBalances.from_responses(responses, id_to_params)}
end
@@ -233,7 +246,7 @@ defmodule EthereumJSONRPC do
@spec fetch_beneficiaries([block_number], json_rpc_named_arguments) ::
{:ok, FetchedBeneficiaries.t()} | {:error, reason :: term} | :ignore
def fetch_beneficiaries(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do
- filtered_block_numbers = block_numbers_in_range(block_numbers)
+ filtered_block_numbers = RangesHelper.filter_traceable_block_numbers(block_numbers)
Keyword.fetch!(json_rpc_named_arguments, :variant).fetch_beneficiaries(
filtered_block_numbers,
@@ -266,12 +279,22 @@ defmodule EthereumJSONRPC do
@doc """
Fetches blocks by block number list.
"""
- @spec fetch_blocks_by_numbers([block_number()], json_rpc_named_arguments) ::
+ @spec fetch_blocks_by_numbers([block_number()], json_rpc_named_arguments, boolean()) ::
{:ok, Blocks.t()} | {:error, reason :: term}
- def fetch_blocks_by_numbers(block_numbers, json_rpc_named_arguments) do
+ def fetch_blocks_by_numbers(block_numbers, json_rpc_named_arguments, with_transactions? \\ true) do
block_numbers
|> Enum.map(fn number -> %{number: number} end)
- |> fetch_blocks_by_params(&Block.ByNumber.request/1, json_rpc_named_arguments)
+ |> fetch_blocks_by_params(&Block.ByNumber.request(&1, with_transactions?), json_rpc_named_arguments)
+ end
+
+ @doc """
+ Fetches block by "t:tag/0".
+ """
+ @spec fetch_block_by_tag(tag(), json_rpc_named_arguments) ::
+ {:ok, Blocks.t()} | {:error, reason :: :invalid_tag | :not_found | term()}
+ def fetch_block_by_tag(tag, json_rpc_named_arguments) when tag in ~w(earliest latest pending safe) do
+ [%{tag: tag}]
+ |> fetch_blocks_by_params(&Block.ByTag.request/1, json_rpc_named_arguments)
end
@doc """
@@ -308,10 +331,9 @@ defmodule EthereumJSONRPC do
"""
@spec fetch_block_number_by_tag(tag(), json_rpc_named_arguments) ::
{:ok, non_neg_integer()} | {:error, reason :: :invalid_tag | :not_found | term()}
- def fetch_block_number_by_tag(tag, json_rpc_named_arguments) when tag in ~w(earliest latest pending) do
- %{id: 0, tag: tag}
- |> Block.ByTag.request()
- |> json_rpc(json_rpc_named_arguments)
+ def fetch_block_number_by_tag(tag, json_rpc_named_arguments) when tag in ~w(earliest latest pending safe) do
+ tag
+ |> fetch_block_by_tag(json_rpc_named_arguments)
|> Block.ByTag.number_from_result()
end
@@ -329,7 +351,7 @@ defmodule EthereumJSONRPC do
Fetches internal transactions for entire blocks from variant API.
"""
def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do
- filtered_block_numbers = block_numbers_in_range(block_numbers)
+ filtered_block_numbers = RangesHelper.filter_traceable_block_numbers(block_numbers)
Keyword.fetch!(json_rpc_named_arguments, :variant).fetch_block_internal_transactions(
filtered_block_numbers,
@@ -337,15 +359,6 @@ defmodule EthereumJSONRPC do
)
end
- def block_numbers_in_range(block_numbers) do
- min_block = first_block_to_fetch(:trace_first_block)
-
- block_numbers
- |> Enum.filter(fn block_number ->
- block_number >= min_block
- end)
- end
-
@doc """
Retrieves traces from variant API.
"""
@@ -376,7 +389,7 @@ defmodule EthereumJSONRPC do
@doc """
Assigns an id to each set of params in `params_list` for batch request-response correlation
"""
- @spec id_to_params([params]) :: %{id => params} when id: non_neg_integer(), params: map()
+ @spec id_to_params([params]) :: %{id => params} when id: non_neg_integer(), params: any()
def id_to_params(params_list) do
params_list
|> Stream.with_index()
@@ -550,12 +563,6 @@ defmodule EthereumJSONRPC do
end
end
- def first_block_to_fetch(config) do
- string_value = Application.get_env(:indexer, config)
-
- case Integer.parse(string_value) do
- {integer, ""} -> integer
- _ -> 0
- end
- end
+ defp chunk_requests(requests, nil), do: requests
+ defp chunk_requests(requests, chunk_size), do: Enum.chunk_every(requests, chunk_size)
end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex
index 5737aa909651..a3a32ddc0c9c 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex
@@ -8,8 +8,23 @@ defmodule EthereumJSONRPC.Block do
alias EthereumJSONRPC.{Transactions, Uncles, Withdrawals}
+ if Application.compile_env(:explorer, :chain_type) == "rsk" do
+ @rootstock_fields quote(
+ do: [
+ bitcoin_merged_mining_header: EthereumJSONRPC.data(),
+ bitcoin_merged_mining_coinbase_transaction: EthereumJSONRPC.data(),
+ bitcoin_merged_mining_merkle_proof: EthereumJSONRPC.data(),
+ hash_for_merged_mining: EthereumJSONRPC.data(),
+ minimum_gas_price: non_neg_integer()
+ ]
+ )
+ else
+ @rootstock_fields quote(do: [])
+ end
+
@type elixir :: %{String.t() => non_neg_integer | DateTime.t() | String.t() | nil}
@type params :: %{
+ unquote_splicing(@rootstock_fields),
difficulty: pos_integer(),
extra_data: EthereumJSONRPC.hash(),
gas_limit: non_neg_integer(),
@@ -67,8 +82,17 @@ defmodule EthereumJSONRPC.Block do
* `uncles`: `t:list/0` of
[uncles](https://bitcoin.stackexchange.com/questions/39329/in-ethereum-what-is-an-uncle-block)
`t:EthereumJSONRPC.hash/0`.
- * `"baseFeePerGas"` - `t:EthereumJSONRPC.quantity/0` of wei to denote amount of fee burned per unit gas used. Introduced in [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md)
+ * `"baseFeePerGas"` - `t:EthereumJSONRPC.quantity/0` of wei to denote amount of fee burnt per unit gas used. Introduced in [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md)
* `"withdrawalsRoot"` - `t:EthereumJSONRPC.hash/0` of the root of the withdrawals.
+ #{if Application.compile_env(:explorer, :chain_type) == "rsk" do
+ """
+ * `"minimumGasPrice"` - `t:EthereumJSONRPC.quantity/0` of the minimum gas price for this block.
+ * `"bitcoinMergedMiningHeader"` - `t:EthereumJSONRPC.data/0` of the Bitcoin merged mining header.
+ * `"bitcoinMergedMiningCoinbaseTransaction"` - `t:EthereumJSONRPC.data/0` of the Bitcoin merged mining coinbase transaction.
+ * `"bitcoinMergedMiningMerkleProof"` - `t:EthereumJSONRPC.data/0` of the Bitcoin merged mining merkle proof.
+ * `"hashForMergedMining"` - `t:EthereumJSONRPC.data/0` of the hash for merged mining.
+ """
+ end}
"""
@type t :: %{String.t() => EthereumJSONRPC.data() | EthereumJSONRPC.hash() | EthereumJSONRPC.quantity() | nil}
@@ -119,7 +143,17 @@ defmodule EthereumJSONRPC.Block do
...> "timestamp" => Timex.parse!("2017-12-15T21:03:30Z", "{ISO:Extended:Z}"),
...> "totalDifficulty" => 340282366920938463463374607431465668165,
...> "transactions" => [],
- ...> "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
+ ...> "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",\
+ #{if Application.compile_env(:explorer, :chain_type) == "rsk" do
+ """
+
+ ...> "minimumGasPrice" => 345786,
+ ...> "bitcoinMergedMiningHeader" => "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9",
+ ...> "bitcoinMergedMiningCoinbaseTransaction" => "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000",
+ ...> "bitcoinMergedMiningMerkleProof" => "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7",
+ ...> "hashForMergedMining" => "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40",\
+ """
+ end}
...> "uncles" => []
...> }
...> )
@@ -142,7 +176,17 @@ defmodule EthereumJSONRPC.Block do
timestamp: Timex.parse!("2017-12-15T21:03:30Z", "{ISO:Extended:Z}"),
total_difficulty: 340282366920938463463374607431465668165,
transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
- uncles: [],
+ uncles: [],\
+ #{if Application.compile_env(:explorer, :chain_type) == "rsk" do
+ """
+
+ bitcoin_merged_mining_coinbase_transaction: "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000",
+ bitcoin_merged_mining_header: "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9",
+ bitcoin_merged_mining_merkle_proof: "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7",
+ hash_for_merged_mining: "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40",
+ minimum_gas_price: 345786,\
+ """
+ end}
withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
}
@@ -191,34 +235,49 @@ defmodule EthereumJSONRPC.Block do
timestamp: Timex.parse!("2015-07-30T15:32:07Z", "{ISO:Extended:Z}"),
total_difficulty: 1039309006117,
transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
- uncles: [],
+ uncles: [],\
+ #{if Application.compile_env(:explorer, :chain_type) == "rsk" do
+ """
+
+ bitcoin_merged_mining_coinbase_transaction: nil,
+ bitcoin_merged_mining_header: nil,
+ bitcoin_merged_mining_merkle_proof: nil,
+ hash_for_merged_mining: nil,
+ minimum_gas_price: nil,\
+ """
+ end}
withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
}
-
"""
@spec elixir_to_params(elixir) :: params
- def elixir_to_params(
- %{
- "difficulty" => difficulty,
- "extraData" => extra_data,
- "gasLimit" => gas_limit,
- "gasUsed" => gas_used,
- "hash" => hash,
- "logsBloom" => logs_bloom,
- "miner" => miner_hash,
- "number" => number,
- "parentHash" => parent_hash,
- "receiptsRoot" => receipts_root,
- "sha3Uncles" => sha3_uncles,
- "size" => size,
- "stateRoot" => state_root,
- "timestamp" => timestamp,
- "totalDifficulty" => total_difficulty,
- "transactionsRoot" => transactions_root,
- "uncles" => uncles,
- "baseFeePerGas" => base_fee_per_gas
- } = elixir
- ) do
+ def elixir_to_params(elixir) do
+ elixir
+ |> do_elixir_to_params()
+ |> chain_type_fields(elixir)
+ end
+
+ defp do_elixir_to_params(
+ %{
+ "difficulty" => difficulty,
+ "extraData" => extra_data,
+ "gasLimit" => gas_limit,
+ "gasUsed" => gas_used,
+ "hash" => hash,
+ "logsBloom" => logs_bloom,
+ "miner" => miner_hash,
+ "number" => number,
+ "parentHash" => parent_hash,
+ "receiptsRoot" => receipts_root,
+ "sha3Uncles" => sha3_uncles,
+ "size" => size,
+ "stateRoot" => state_root,
+ "timestamp" => timestamp,
+ "totalDifficulty" => total_difficulty,
+ "transactionsRoot" => transactions_root,
+ "uncles" => uncles,
+ "baseFeePerGas" => base_fee_per_gas
+ } = elixir
+ ) do
%{
difficulty: difficulty,
extra_data: extra_data,
@@ -245,27 +304,27 @@ defmodule EthereumJSONRPC.Block do
}
end
- def elixir_to_params(
- %{
- "difficulty" => difficulty,
- "extraData" => extra_data,
- "gasLimit" => gas_limit,
- "gasUsed" => gas_used,
- "hash" => hash,
- "logsBloom" => logs_bloom,
- "miner" => miner_hash,
- "number" => number,
- "parentHash" => parent_hash,
- "receiptsRoot" => receipts_root,
- "sha3Uncles" => sha3_uncles,
- "size" => size,
- "stateRoot" => state_root,
- "timestamp" => timestamp,
- "transactionsRoot" => transactions_root,
- "uncles" => uncles,
- "baseFeePerGas" => base_fee_per_gas
- } = elixir
- ) do
+ defp do_elixir_to_params(
+ %{
+ "difficulty" => difficulty,
+ "extraData" => extra_data,
+ "gasLimit" => gas_limit,
+ "gasUsed" => gas_used,
+ "hash" => hash,
+ "logsBloom" => logs_bloom,
+ "miner" => miner_hash,
+ "number" => number,
+ "parentHash" => parent_hash,
+ "receiptsRoot" => receipts_root,
+ "sha3Uncles" => sha3_uncles,
+ "size" => size,
+ "stateRoot" => state_root,
+ "timestamp" => timestamp,
+ "transactionsRoot" => transactions_root,
+ "uncles" => uncles,
+ "baseFeePerGas" => base_fee_per_gas
+ } = elixir
+ ) do
%{
difficulty: difficulty,
extra_data: extra_data,
@@ -291,27 +350,27 @@ defmodule EthereumJSONRPC.Block do
}
end
- def elixir_to_params(
- %{
- "difficulty" => difficulty,
- "extraData" => extra_data,
- "gasLimit" => gas_limit,
- "gasUsed" => gas_used,
- "hash" => hash,
- "logsBloom" => logs_bloom,
- "miner" => miner_hash,
- "number" => number,
- "parentHash" => parent_hash,
- "receiptsRoot" => receipts_root,
- "sha3Uncles" => sha3_uncles,
- "size" => size,
- "stateRoot" => state_root,
- "timestamp" => timestamp,
- "totalDifficulty" => total_difficulty,
- "transactionsRoot" => transactions_root,
- "uncles" => uncles
- } = elixir
- ) do
+ defp do_elixir_to_params(
+ %{
+ "difficulty" => difficulty,
+ "extraData" => extra_data,
+ "gasLimit" => gas_limit,
+ "gasUsed" => gas_used,
+ "hash" => hash,
+ "logsBloom" => logs_bloom,
+ "miner" => miner_hash,
+ "number" => number,
+ "parentHash" => parent_hash,
+ "receiptsRoot" => receipts_root,
+ "sha3Uncles" => sha3_uncles,
+ "size" => size,
+ "stateRoot" => state_root,
+ "timestamp" => timestamp,
+ "totalDifficulty" => total_difficulty,
+ "transactionsRoot" => transactions_root,
+ "uncles" => uncles
+ } = elixir
+ ) do
%{
difficulty: difficulty,
extra_data: extra_data,
@@ -338,26 +397,26 @@ defmodule EthereumJSONRPC.Block do
end
# Geth: a response from eth_getblockbyhash for uncle blocks is without `totalDifficulty` param
- def elixir_to_params(
- %{
- "difficulty" => difficulty,
- "extraData" => extra_data,
- "gasLimit" => gas_limit,
- "gasUsed" => gas_used,
- "hash" => hash,
- "logsBloom" => logs_bloom,
- "miner" => miner_hash,
- "number" => number,
- "parentHash" => parent_hash,
- "receiptsRoot" => receipts_root,
- "sha3Uncles" => sha3_uncles,
- "size" => size,
- "stateRoot" => state_root,
- "timestamp" => timestamp,
- "transactionsRoot" => transactions_root,
- "uncles" => uncles
- } = elixir
- ) do
+ defp do_elixir_to_params(
+ %{
+ "difficulty" => difficulty,
+ "extraData" => extra_data,
+ "gasLimit" => gas_limit,
+ "gasUsed" => gas_used,
+ "hash" => hash,
+ "logsBloom" => logs_bloom,
+ "miner" => miner_hash,
+ "number" => number,
+ "parentHash" => parent_hash,
+ "receiptsRoot" => receipts_root,
+ "sha3Uncles" => sha3_uncles,
+ "size" => size,
+ "stateRoot" => state_root,
+ "timestamp" => timestamp,
+ "transactionsRoot" => transactions_root,
+ "uncles" => uncles
+ } = elixir
+ ) do
%{
difficulty: difficulty,
extra_data: extra_data,
@@ -382,6 +441,23 @@ defmodule EthereumJSONRPC.Block do
}
end
+ defp chain_type_fields(params, elixir) do
+ case Application.get_env(:explorer, :chain_type) do
+ "rsk" ->
+ params
+ |> Map.merge(%{
+ minimum_gas_price: Map.get(elixir, "minimumGasPrice"),
+ bitcoin_merged_mining_header: Map.get(elixir, "bitcoinMergedMiningHeader"),
+ bitcoin_merged_mining_coinbase_transaction: Map.get(elixir, "bitcoinMergedMiningCoinbaseTransaction"),
+ bitcoin_merged_mining_merkle_proof: Map.get(elixir, "bitcoinMergedMiningMerkleProof"),
+ hash_for_merged_mining: Map.get(elixir, "hashForMergedMining")
+ })
+
+ _ ->
+ params
+ end
+ end
+
@doc """
Get `t:EthereumJSONRPC.Transactions.elixir/0` from `t:elixir/0`
@@ -685,11 +761,16 @@ defmodule EthereumJSONRPC.Block do
end
defp entry_to_elixir({key, quantity}, _block)
- when key in ~w(difficulty gasLimit gasUsed minimumGasPrice baseFeePerGas number size cumulativeDifficulty totalDifficulty paidFees) and
+ when key in ~w(difficulty gasLimit gasUsed minimumGasPrice baseFeePerGas number size cumulativeDifficulty totalDifficulty paidFees minimumGasPrice) and
not is_nil(quantity) do
{key, quantity_to_integer(quantity)}
end
+ # to be merged with clause above ^
+ defp entry_to_elixir({key, _quantity}, _block) when key in ~w(blobGasUsed excessBlobGas) do
+ {:ignore, :ignore}
+ end
+
# Size and totalDifficulty may be `nil` for uncle blocks
defp entry_to_elixir({key, nil}, _block) when key in ~w(size totalDifficulty) do
{key, nil}
@@ -700,15 +781,15 @@ defmodule EthereumJSONRPC.Block do
# hash format
defp entry_to_elixir({key, _} = entry, _block)
when key in ~w(author extraData hash logsBloom miner mixHash nonce parentHash receiptsRoot sealFields sha3Uncles
- signature stateRoot step transactionsRoot uncles withdrawalsRoot),
+ signature stateRoot step transactionsRoot uncles withdrawalsRoot bitcoinMergedMiningHeader bitcoinMergedMiningCoinbaseTransaction bitcoinMergedMiningMerkleProof hashForMergedMining),
do: entry
defp entry_to_elixir({"timestamp" = key, timestamp}, _block) do
{key, timestamp_to_datetime(timestamp)}
end
- defp entry_to_elixir({"transactions" = key, transactions}, _block) do
- {key, Transactions.to_elixir(transactions)}
+ defp entry_to_elixir({"transactions" = key, transactions}, %{"timestamp" => block_timestamp}) do
+ {key, Transactions.to_elixir(transactions, timestamp_to_datetime(block_timestamp))}
end
defp entry_to_elixir({"withdrawals" = key, nil}, _block) do
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_number.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_number.ex
index 80ec9b1cd105..5a1cf1eeca41 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_number.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_number.ex
@@ -5,7 +5,17 @@ defmodule EthereumJSONRPC.Block.ByNumber do
import EthereumJSONRPC, only: [integer_to_quantity: 1]
- def request(%{id: id, number: number}) do
- EthereumJSONRPC.request(%{id: id, method: "eth_getBlockByNumber", params: [integer_to_quantity(number), true]})
+ alias EthereumJSONRPC.Transport
+
+ @spec request(map(), boolean(), boolean()) :: Transport.request()
+ def request(%{id: id, number: number}, hydrated \\ true, int_to_qty \\ true) do
+ block_number =
+ if int_to_qty do
+ integer_to_quantity(number)
+ else
+ number
+ end
+
+ EthereumJSONRPC.request(%{id: id, method: "eth_getBlockByNumber", params: [block_number, hydrated]})
end
end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_tag.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_tag.ex
index fa4a9b83c990..d257f4e506c5 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_tag.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_tag.ex
@@ -5,6 +5,7 @@ defmodule EthereumJSONRPC.Block.ByTag do
"""
import EthereumJSONRPC, only: [quantity_to_integer: 1]
+ alias EthereumJSONRPC.Blocks
def request(%{id: id, tag: tag}) when is_binary(tag) do
EthereumJSONRPC.request(%{id: id, method: "eth_getBlockByNumber", params: [tag, false]})
@@ -16,6 +17,10 @@ defmodule EthereumJSONRPC.Block.ByTag do
{:ok, quantity_to_integer(quantity)}
end
+ def number_from_result({:ok, %Blocks{blocks_params: []}}), do: {:error, :not_found}
+
+ def number_from_result({:ok, %Blocks{blocks_params: [%{number: number}]}}), do: {:ok, number}
+
def number_from_result({:ok, nil}), do: {:error, :not_found}
def number_from_result({:error, %{"code" => -32602}}), do: {:error, :invalid_tag}
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex
index a501a16bc817..d9a697c1acbd 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex
@@ -116,7 +116,17 @@ defmodule EthereumJSONRPC.Blocks do
timestamp: Timex.parse!("1970-01-01T00:00:00Z", "{ISO:Extended:Z}"),
total_difficulty: 131072,
transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
- uncles: ["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273311"],
+ uncles: ["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273311"],\
+ #{if Application.compile_env(:explorer, :chain_type) == "rsk" do
+ """
+
+ bitcoin_merged_mining_coinbase_transaction: nil,
+ bitcoin_merged_mining_header: nil,
+ bitcoin_merged_mining_merkle_proof: nil,
+ hash_for_merged_mining: nil,
+ minimum_gas_price: nil,\
+ """
+ end}
withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
}
]
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex
index 221c082b0218..e667ce8eaa89 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex
@@ -13,7 +13,7 @@ defmodule EthereumJSONRPC.Encoder do
"""
@spec encode_function_call(ABI.FunctionSelector.t(), [term()]) :: String.t()
def encode_function_call(function_selector, args) when is_list(args) do
- parsed_args = parse_args(args)
+ parsed_args = parse_args(args, function_selector.types)
encoded_args =
function_selector
@@ -25,16 +25,22 @@ defmodule EthereumJSONRPC.Encoder do
def encode_function_call(function_selector, args), do: encode_function_call(function_selector, [args])
- defp parse_args(args) when is_list(args) do
+ defp parse_args(args, types) when is_list(args) do
args
- |> Enum.map(&parse_args/1)
+ |> Enum.zip(types)
+ |> Enum.map(fn {arg, type} ->
+ parse_args(arg, type)
+ end)
end
- defp parse_args(<<"0x", hexadecimal_digits::binary>>), do: Base.decode16!(hexadecimal_digits, case: :mixed)
+ defp parse_args(<>, type) when type in [:string, "string"],
+ do: hexadecimal_digits |> Base.encode16() |> try_to_decode()
+
+ defp parse_args(<<"0x", hexadecimal_digits::binary>>, _type), do: Base.decode16!(hexadecimal_digits, case: :mixed)
- defp parse_args(<>), do: try_to_decode(hexadecimal_digits)
+ defp parse_args(<>, _type), do: try_to_decode(hexadecimal_digits)
- defp parse_args(arg), do: arg
+ defp parse_args(arg, _type), do: arg
defp try_to_decode(hexadecimal_digits) do
case Base.decode16(hexadecimal_digits, case: :mixed) do
@@ -69,7 +75,7 @@ defmodule EthereumJSONRPC.Encoder do
end
end
- def decode_result(result, selectors, _leave_error_as_map) when is_list(selectors) do
+ def decode_result(%{id: id, result: _result} = result, selectors, _leave_error_as_map) when is_list(selectors) do
selectors
|> Enum.map(fn selector ->
try do
@@ -78,7 +84,7 @@ defmodule EthereumJSONRPC.Encoder do
_ -> :error
end
end)
- |> Enum.find(fn decode ->
+ |> Enum.find({id, {:error, :unable_to_decode}}, fn decode ->
case decode do
{_id, {:ok, _}} -> true
_ -> false
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex
index 7e935f54ff5f..96379d75bb5c 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex
@@ -8,7 +8,7 @@ defmodule EthereumJSONRPC.Geth do
import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2, request: 1]
alias EthereumJSONRPC.{FetchedBalance, FetchedCode, PendingTransaction, Utility.CommonHelper}
- alias EthereumJSONRPC.Geth.{Calls, Tracer}
+ alias EthereumJSONRPC.Geth.{Calls, PolygonTracer, Tracer}
@behaviour EthereumJSONRPC.Variant
@@ -63,12 +63,65 @@ defmodule EthereumJSONRPC.Geth do
def fetch_first_trace(_transactions_params, _json_rpc_named_arguments), do: :ignore
@doc """
- Internal transaction fetching for entire blocks is not currently supported for Geth.
-
- To signal to the caller that fetching is not supported, `:ignore` is returned.
+ Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Geth trace URL.
"""
@impl EthereumJSONRPC.Variant
- def fetch_block_internal_transactions(_block_range, _json_rpc_named_arguments), do: :ignore
+ def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) do
+ id_to_params = id_to_params(block_numbers)
+
+ with {:ok, blocks_responses} <-
+ id_to_params
+ |> debug_trace_block_by_number_requests()
+ |> json_rpc(json_rpc_named_arguments),
+ :ok <- check_errors_exist(blocks_responses, id_to_params) do
+ transactions_params = to_transactions_params(blocks_responses, id_to_params)
+
+ {transactions_id_to_params, transactions_responses} =
+ Enum.reduce(transactions_params, {%{}, []}, fn {params, calls}, {id_to_params_acc, calls_acc} ->
+ {Map.put(id_to_params_acc, params[:id], params), [calls | calls_acc]}
+ end)
+
+ debug_trace_transaction_responses_to_internal_transactions_params(
+ transactions_responses,
+ transactions_id_to_params,
+ json_rpc_named_arguments
+ )
+ end
+ end
+
+ defp check_errors_exist(blocks_responses, id_to_params) do
+ blocks_responses
+ |> EthereumJSONRPC.sanitize_responses(id_to_params)
+ |> Enum.reduce([], fn
+ %{result: _result}, acc -> acc
+ %{error: error}, acc -> [error | acc]
+ end)
+ |> case do
+ [] -> :ok
+ errors -> {:error, errors}
+ end
+ end
+
+ defp to_transactions_params(blocks_responses, id_to_params) do
+ Enum.reduce(blocks_responses, [], fn %{id: id, result: tx_result}, blocks_acc ->
+ extract_transactions_params(Map.fetch!(id_to_params, id), tx_result) ++ blocks_acc
+ end)
+ end
+
+ defp extract_transactions_params(block_number, tx_result) do
+ tx_result
+ |> Enum.reduce({[], 0}, fn %{"txHash" => tx_hash, "result" => calls_result}, {tx_acc, counter} ->
+ {
+ [
+ {%{block_number: block_number, hash_data: tx_hash, transaction_index: counter, id: counter},
+ %{id: counter, result: calls_result}}
+ | tx_acc
+ ],
+ counter + 1
+ }
+ end)
+ |> elem(0)
+ end
@doc """
Fetches the pending transactions from the Geth node.
@@ -84,6 +137,10 @@ defmodule EthereumJSONRPC.Geth do
end)
end
+ defp debug_trace_block_by_number_requests(id_to_params) do
+ Enum.map(id_to_params, &debug_trace_block_by_number_request/1)
+ end
+
@tracer_path "priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js"
@external_resource @tracer_path
@tracer File.read!(@tracer_path)
@@ -92,25 +149,51 @@ defmodule EthereumJSONRPC.Geth do
debug_trace_transaction_timeout =
Application.get_env(:ethereum_jsonrpc, __MODULE__)[:debug_trace_transaction_timeout]
- tracer =
- case Application.get_env(:ethereum_jsonrpc, __MODULE__)[:tracer] do
- "js" -> @tracer
- "call_tracer" -> "callTracer"
- end
-
request(%{
id: id,
method: "debug_traceTransaction",
- params: [hash_data, %{tracer: tracer, timeout: debug_trace_transaction_timeout}]
+ params: [hash_data, %{timeout: debug_trace_transaction_timeout} |> Map.merge(tracer_params())]
})
end
+ defp debug_trace_block_by_number_request({id, block_number}) do
+ request(%{
+ id: id,
+ method: "debug_traceBlockByNumber",
+ params: [integer_to_quantity(block_number), tracer_params()]
+ })
+ end
+
+ defp tracer_params do
+ cond do
+ tracer_type() == "js" ->
+ %{"tracer" => @tracer}
+
+ tracer_type() in ~w(opcode polygon_edge) ->
+ %{
+ "enableMemory" => true,
+ "disableStack" => false,
+ "disableStorage" => true,
+ "enableReturnData" => false
+ }
+
+ true ->
+ %{"tracer" => "callTracer"}
+ end
+ end
+
defp debug_trace_transaction_responses_to_internal_transactions_params(
[%{result: %{"structLogs" => _}} | _] = responses,
id_to_params,
json_rpc_named_arguments
)
when is_map(id_to_params) do
+ if tracer_type() not in ["opcode", "polygon_edge"] do
+ Logger.warning(
+ "structLogs found in debug_traceTransaction response, you should probably change your INDEXER_INTERNAL_TRANSACTIONS_TRACER_TYPE env value"
+ )
+ end
+
with {:ok, receipts} <-
id_to_params
|> Enum.map(fn {id, %{hash_data: hash_data}} ->
@@ -126,12 +209,21 @@ defmodule EthereumJSONRPC.Geth do
receipts_map = Enum.into(receipts, %{}, fn %{id: id, result: receipt} -> {id, receipt} end)
txs_map = Enum.into(txs, %{}, fn %{id: id, result: tx} -> {id, tx} end)
+ tracer =
+ if Application.get_env(:ethereum_jsonrpc, __MODULE__)[:tracer] == "polygon_edge",
+ do: PolygonTracer,
+ else: Tracer
+
responses
- |> Enum.map(fn %{id: id, result: %{"structLogs" => _} = result} ->
- debug_trace_transaction_response_to_internal_transactions_params(
- %{id: id, result: Tracer.replay(result, Map.fetch!(receipts_map, id), Map.fetch!(txs_map, id))},
- id_to_params
- )
+ |> Enum.map(fn
+ %{result: %{"structLogs" => nil}} ->
+ []
+
+ %{id: id, result: %{"structLogs" => _} = result} ->
+ debug_trace_transaction_response_to_internal_transactions_params(
+ %{id: id, result: tracer.replay(result, Map.fetch!(receipts_map, id), Map.fetch!(txs_map, id))},
+ id_to_params
+ )
end)
|> reduce_internal_transactions_params()
|> fetch_missing_data(json_rpc_named_arguments)
@@ -159,7 +251,7 @@ defmodule EthereumJSONRPC.Geth do
{id, %{created_contract_address_hash: address, block_number: block_number}} ->
FetchedCode.request(%{id: id, block_quantity: integer_to_quantity(block_number), address: address})
- {id, %{type: "selfdestruct", from: hash_data, block_number: block_number}} ->
+ {id, %{type: "selfdestruct", from_address_hash: hash_data, block_number: block_number}} ->
FetchedBalance.request(%{id: id, block_quantity: integer_to_quantity(block_number), hash_data: hash_data})
_ ->
@@ -245,7 +337,7 @@ defmodule EthereumJSONRPC.Geth do
def prepare_calls(calls) do
case Application.get_env(:ethereum_jsonrpc, __MODULE__)[:tracer] do
"call_tracer" -> {calls, 0} |> parse_call_tracer_calls([], [], false) |> Enum.reverse()
- "js" -> calls
+ _ -> calls
end
end
@@ -253,52 +345,53 @@ defmodule EthereumJSONRPC.Geth do
defp parse_call_tracer_calls([], acc, _trace_address, _inner?), do: acc
defp parse_call_tracer_calls({%{"type" => 0}, _}, acc, _trace_address, _inner?), do: acc
- defp parse_call_tracer_calls(
- {%{"type" => type, "from" => from} = call, index},
- acc,
- trace_address,
- inner?
- )
- when type in ~w(CALL CALLCODE DELEGATECALL STATICCALL CREATE CREATE2 SELFDESTRUCT REWARD) do
- new_trace_address = [index | trace_address]
-
- formatted_call =
- %{
- "type" => if(type in ~w(CALL CALLCODE DELEGATECALL STATICCALL), do: "call", else: String.downcase(type)),
- "callType" => String.downcase(type),
- "from" => from,
- "to" => Map.get(call, "to", "0x"),
- "createdContractAddressHash" => Map.get(call, "to", "0x"),
- "value" => Map.get(call, "value", "0x0"),
- "gas" => Map.get(call, "gas", "0x0"),
- "gasUsed" => Map.get(call, "gasUsed", "0x0"),
- "input" => Map.get(call, "input", "0x"),
- "init" => Map.get(call, "input", "0x"),
- "createdContractCode" => Map.get(call, "output", "0x"),
- "traceAddress" => if(inner?, do: Enum.reverse(new_trace_address), else: []),
- "error" => call["error"]
- }
- |> case do
- %{"error" => nil} = ok_call ->
- ok_call
- |> Map.delete("error")
- # to handle staticcall, all other cases handled by EthereumJSONRPC.Geth.Call.elixir_to_internal_transaction_params/1
- |> Map.put("output", Map.get(call, "output", "0x"))
-
- error_call ->
- error_call
- end
-
- parse_call_tracer_calls(
- Map.get(call, "calls", []),
- [formatted_call | acc],
- if(inner?, do: new_trace_address, else: [])
- )
+ defp parse_call_tracer_calls({%{"type" => type}, _}, [last | acc], _trace_address, _inner?)
+ when type in ["STOP", "stop"] do
+ [Map.put(last, "error", "execution stopped") | acc]
end
- defp parse_call_tracer_calls({call, _}, acc, _trace_address, _inner?) do
- Logger.warning("Call from a callTracer with an unknown type: #{inspect(call)}")
- acc
+ defp parse_call_tracer_calls({%{"type" => upcase_type, "from" => from} = call, index}, acc, trace_address, inner?) do
+ case String.downcase(upcase_type) do
+ type when type in ~w(call callcode delegatecall staticcall create create2 selfdestruct revert stop) ->
+ new_trace_address = [index | trace_address]
+
+ formatted_call =
+ %{
+ "type" => if(type in ~w(call callcode delegatecall staticcall), do: "call", else: type),
+ "callType" => type,
+ "from" => from,
+ "to" => Map.get(call, "to", "0x"),
+ "createdContractAddressHash" => Map.get(call, "to", "0x"),
+ "value" => Map.get(call, "value", "0x0"),
+ "gas" => Map.get(call, "gas", "0x0"),
+ "gasUsed" => Map.get(call, "gasUsed", "0x0"),
+ "input" => Map.get(call, "input", "0x"),
+ "init" => Map.get(call, "input", "0x"),
+ "createdContractCode" => Map.get(call, "output", "0x"),
+ "traceAddress" => if(inner?, do: Enum.reverse(new_trace_address), else: []),
+ "error" => call["error"]
+ }
+ |> case do
+ %{"error" => nil} = ok_call ->
+ ok_call
+ |> Map.delete("error")
+ # to handle staticcall, all other cases handled by EthereumJSONRPC.Geth.Call.elixir_to_internal_transaction_params/1
+ |> Map.put("output", Map.get(call, "output", "0x"))
+
+ error_call ->
+ error_call
+ end
+
+ parse_call_tracer_calls(
+ Map.get(call, "calls", []),
+ [formatted_call | acc],
+ if(inner?, do: new_trace_address, else: [])
+ )
+
+ _unknown_type ->
+ Logger.warning("Call from a callTracer with an unknown type: #{inspect(call)}")
+ acc
+ end
end
defp parse_call_tracer_calls(calls, acc, trace_address, _inner) when is_list(calls) do
@@ -338,4 +431,8 @@ defmodule EthereumJSONRPC.Geth do
defp finalize_internal_transactions_params({:error, acc_reasons}) do
{:error, Enum.reverse(acc_reasons)}
end
+
+ defp tracer_type do
+ Application.get_env(:ethereum_jsonrpc, __MODULE__)[:tracer]
+ end
end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex
index dfdbb965fcd4..c90c652db3f4 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex
@@ -511,4 +511,33 @@ defmodule EthereumJSONRPC.Geth.Call do
value: value
}
end
+
+ defp elixir_to_internal_transaction_params(%{
+ "blockNumber" => block_number,
+ "transactionIndex" => transaction_index,
+ "transactionHash" => transaction_hash,
+ "index" => index,
+ "traceAddress" => trace_address,
+ "type" => "stop" = type,
+ "from" => from_address_hash,
+ "input" => input,
+ "gas" => gas,
+ "gasUsed" => gas_used,
+ "value" => value
+ }) do
+ %{
+ block_number: block_number,
+ transaction_index: transaction_index,
+ transaction_hash: transaction_hash,
+ index: index,
+ trace_address: trace_address,
+ type: type,
+ from_address_hash: from_address_hash,
+ input: input,
+ gas: gas,
+ gas_used: gas_used,
+ value: value,
+ error: "execution stopped"
+ }
+ end
end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/polygon_tracer.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/polygon_tracer.ex
new file mode 100644
index 000000000000..edc16207c961
--- /dev/null
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/polygon_tracer.ex
@@ -0,0 +1,355 @@
+defmodule EthereumJSONRPC.Geth.PolygonTracer do
+ @moduledoc """
+ Elixir implementation of a custom tracer (`priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js`)
+ for Polygon edge nodes that don't support specifying tracer in [debug_traceTransaction](https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugtracetransaction) calls.
+ """
+
+ import EthereumJSONRPC, only: [integer_to_quantity: 1, quantity_to_integer: 1]
+
+ @burn_address "0x0000000000000000000000000000000000000000"
+
+ def replay(
+ %{"structLogs" => logs, "gas" => top_call_gas, "returnValue" => return_value} = result,
+ %{"contractAddress" => contract_address},
+ %{"from" => from, "to" => to, "value" => value, "input" => input}
+ )
+ when is_list(logs) do
+ top =
+ to
+ |> if do
+ %{
+ "type" => "call",
+ "callType" => "call",
+ "to" => to,
+ "input" => input,
+ "output" => Map.get(result, "return", "0x" <> Map.get(result, "returnValue", ""))
+ }
+ else
+ %{
+ "type" => "create",
+ "init" => input,
+ "createdContractAddressHash" => contract_address,
+ "createdContractCode" => "0x" <> return_value
+ }
+ end
+ |> Map.merge(%{
+ "from" => from,
+ "traceAddress" => [],
+ "value" => value,
+ "gas" => 0,
+ "gasUsed" => 0
+ })
+
+ ctx = %{
+ depth: 1,
+ stack: [top],
+ trace_address: [0],
+ calls: [[]],
+ descended: false
+ }
+
+ logs
+ |> Enum.reduce(ctx, &step/2)
+ |> finalize(top_call_gas)
+ end
+
+ defp step(%{"error" => _}, %{stack: [%{"error" => _} | _]} = ctx), do: ctx
+
+ defp step(
+ %{"error" => _} = log,
+ %{
+ depth: stack_depth,
+ stack: [call | stack],
+ trace_address: [_, trace_index | trace_address],
+ calls: [subsubcalls, subcalls | calls]
+ } = ctx
+ ) do
+ call = process_return(log, Map.put(call, "error", "error"))
+
+ subsubcalls =
+ subsubcalls
+ |> Stream.map(fn
+ subcalls when is_list(subcalls) -> subcalls
+ subcall when is_map(subcall) -> %{subcall | "from" => call["createdContractAddressHash"] || call["to"]}
+ end)
+ |> Enum.reverse()
+
+ %{
+ ctx
+ | depth: stack_depth - 1,
+ stack: stack,
+ trace_address: [trace_index + 1 | trace_address],
+ calls: [[subsubcalls, call | subcalls] | calls]
+ }
+ end
+
+ defp step(
+ %{"gas" => log_gas} = log,
+ %{
+ stack: [%{"gas" => call_gas} = call | stack],
+ descended: true
+ } = ctx
+ ) do
+ gas = max(call_gas, log_gas)
+ call = %{call | "gas" => gas}
+ step(log, %{ctx | stack: [call | stack], descended: false})
+ end
+
+ defp step(
+ %{"depth" => log_depth} = log,
+ %{
+ depth: stack_depth,
+ stack: [call | stack],
+ trace_address: [_, trace_index | trace_address],
+ calls: [subsubcalls, subcalls | calls]
+ } = ctx
+ )
+ when log_depth == stack_depth - 1 do
+ call = process_return(log, call)
+
+ subsubcalls =
+ subsubcalls
+ |> Stream.map(fn
+ subcalls when is_list(subcalls) -> subcalls
+ subcall when is_map(subcall) -> %{subcall | "from" => call["createdContractAddressHash"] || call["to"]}
+ end)
+ |> Enum.reverse()
+
+ step(log, %{
+ ctx
+ | depth: stack_depth - 1,
+ stack: stack,
+ trace_address: [trace_index + 1 | trace_address],
+ calls: [[subsubcalls, call | subcalls] | calls]
+ })
+ end
+
+ defp step(%{"gas" => log_gas, "gasCost" => log_gas_cost} = log, %{stack: [%{"gas" => call_gas} = call | stack]} = ctx) do
+ gas = max(call_gas, log_gas)
+ op(log, %{ctx | stack: [%{call | "gas" => gas, "gasUsed" => gas - log_gas - log_gas_cost} | stack]})
+ end
+
+ defp op(%{"op" => "CREATE"} = log, ctx), do: create_op(log, ctx)
+ defp op(%{"op" => "CREATE2"} = log, ctx), do: create_op(log, ctx, "create2")
+ defp op(%{"op" => "SELFDESTRUCT"} = log, ctx), do: self_destruct_op(log, ctx)
+ defp op(%{"op" => "CALL"} = log, ctx), do: call_op(log, "call", ctx)
+ defp op(%{"op" => "CALLCODE"} = log, ctx), do: call_op(log, "callcode", ctx)
+ defp op(%{"op" => "DELEGATECALL"} = log, ctx), do: call_op(log, "delegatecall", ctx)
+ defp op(%{"op" => "STATICCALL"} = log, ctx), do: call_op(log, "staticcall", ctx)
+ defp op(%{"op" => "REVERT"}, ctx), do: revert_op(ctx)
+ defp op(_, ctx), do: ctx
+
+ defp process_return(
+ %{"stack" => log_stack},
+ %{"type" => create} = call
+ )
+ when create in ~w(create create2) do
+ [ret | _] = Enum.reverse(log_stack)
+
+ ret
+ |> quantity_to_integer()
+ |> case do
+ 0 ->
+ Map.put(call, "error", call["error"] || "internal failure")
+
+ _ ->
+ %{call | "createdContractAddressHash" => ret}
+ end
+ end
+
+ defp process_return(
+ %{"stack" => log_stack, "memory" => log_memory},
+ %{"outputOffset" => out_off, "outputLength" => out_len} = call
+ ) do
+ [ret | _] = Enum.reverse(log_stack)
+
+ ret
+ |> quantity_to_integer()
+ |> case do
+ 0 ->
+ Map.put(call, "error", call["error"] || "internal failure")
+
+ _ ->
+ output =
+ log_memory
+ |> IO.iodata_to_binary()
+ |> String.slice(out_off, out_len)
+
+ %{call | "output" => "0x" <> output}
+ end
+ |> Map.drop(["outputOffset", "outputLength"])
+ end
+
+ defp process_return(_log, call) do
+ call
+ end
+
+ defp create_op(
+ %{"stack" => log_stack, "memory" => log_memory},
+ %{depth: stack_depth, stack: stack, trace_address: trace_address, calls: calls} = ctx,
+ type \\ "create"
+ ) do
+ [value, input_length | _] = Enum.reverse(log_stack)
+
+ init =
+ log_memory
+ |> IO.iodata_to_binary()
+ |> String.slice(0, quantity_to_integer(input_length) * 2)
+
+ call = %{
+ "type" => type,
+ "from" => nil,
+ "traceAddress" => Enum.reverse(trace_address),
+ "init" => "0x" <> init,
+ "gas" => 0,
+ "gasUsed" => 0,
+ "value" => value,
+ "createdContractAddressHash" => nil,
+ "createdContractCode" => "0x"
+ }
+
+ %{
+ ctx
+ | depth: stack_depth + 1,
+ stack: [call | stack],
+ trace_address: [0 | trace_address],
+ calls: [[] | calls],
+ descended: true
+ }
+ end
+
+ defp self_destruct_op(
+ %{"stack" => log_stack, "gas" => log_gas, "gasCost" => log_gas_cost},
+ %{trace_address: [trace_index | trace_address], calls: [subcalls | calls]} = ctx
+ ) do
+ [to | _] = Enum.reverse(log_stack)
+
+ if quantity_to_integer(to) in 1..8 do
+ ctx
+ else
+ call = %{
+ "type" => "selfdestruct",
+ "from" => nil,
+ "to" => to,
+ "traceAddress" => Enum.reverse([trace_index | trace_address]),
+ "gas" => log_gas,
+ "gasUsed" => log_gas_cost,
+ "value" => "0x0"
+ }
+
+ %{ctx | trace_address: [trace_index + 1 | trace_address], calls: [[call | subcalls] | calls]}
+ end
+ end
+
+ defp call_op(
+ %{"stack" => call_stack},
+ call_type,
+ %{
+ depth: stack_depth,
+ stack: stack,
+ trace_address: trace_address,
+ calls: calls
+ } = ctx
+ )
+ when length(call_stack) < 3 do
+ call = %{
+ "type" => "call",
+ "callType" => call_type,
+ "from" => nil,
+ "to" => @burn_address,
+ "traceAddress" => Enum.reverse(trace_address),
+ "input" => "0x",
+ "output" => "0x",
+ "outputOffset" => 0,
+ "outputLength" => 0,
+ "gas" => 0,
+ "gasUsed" => 0,
+ "value" => "0x0"
+ }
+
+ %{
+ ctx
+ | depth: stack_depth + 1,
+ stack: [call | stack],
+ trace_address: [0 | trace_address],
+ calls: [[] | calls],
+ descended: true
+ }
+ end
+
+ defp call_op(
+ %{"stack" => log_stack, "memory" => log_memory},
+ call_type,
+ %{
+ depth: stack_depth,
+ stack: [%{"value" => parent_value} = parent | stack],
+ trace_address: trace_address,
+ calls: calls
+ } = ctx
+ ) do
+ [_, to | log_stack] = Enum.reverse(log_stack)
+
+ {value, [input_length, output_length | _]} =
+ case call_type do
+ "delegatecall" ->
+ {parent_value, log_stack}
+
+ "staticcall" ->
+ {"0x0", log_stack}
+
+ _ ->
+ [value | rest] = log_stack
+ {value, rest}
+ end
+
+ input =
+ log_memory
+ |> IO.iodata_to_binary()
+ |> String.slice(0, quantity_to_integer(input_length || 0) * 2)
+
+ call = %{
+ "type" => "call",
+ "callType" => call_type,
+ "from" => nil,
+ "to" => to,
+ "traceAddress" => Enum.reverse(trace_address),
+ "input" => "0x" <> input,
+ "output" => "0x",
+ "outputOffset" => quantity_to_integer(input_length) * 2,
+ "outputLength" => quantity_to_integer(output_length) * 2,
+ "gas" => 0,
+ "gasUsed" => 0,
+ "value" => value
+ }
+
+ %{
+ ctx
+ | depth: stack_depth + 1,
+ stack: [call, parent | stack],
+ trace_address: [0 | trace_address],
+ calls: [[] | calls],
+ descended: true
+ }
+ end
+
+ defp revert_op(%{stack: [last | stack]} = ctx) do
+ %{ctx | stack: [Map.put(last, "error", "execution reverted") | stack]}
+ end
+
+ defp finalize(%{stack: [top], calls: [calls]}, top_call_gas) do
+ calls =
+ Enum.map(calls, fn
+ subcalls when is_list(subcalls) ->
+ subcalls
+
+ subcall when is_map(subcall) ->
+ %{subcall | "from" => top["createdContractAddressHash"] || top["to"]}
+ end)
+
+ [%{top | "gasUsed" => top_call_gas} | Enum.reverse(calls)]
+ |> List.flatten()
+ |> Enum.map(fn %{"gas" => gas, "gasUsed" => gas_used} = call ->
+ %{call | "gas" => integer_to_quantity(gas), "gasUsed" => gas_used |> max(0) |> integer_to_quantity()}
+ end)
+ end
+end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex
index 8ccf42730caf..92aa18fc3d4c 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex
@@ -311,7 +311,7 @@ defmodule EthereumJSONRPC.Geth.Tracer do
[%{top | "gasUsed" => top_call_gas} | Enum.reverse(calls)]
|> List.flatten()
|> Enum.map(fn %{"gas" => gas, "gasUsed" => gas_used} = call ->
- %{call | "gas" => integer_to_quantity(gas), "gasUsed" => gas_used |> max(0) |> integer_to_quantity()}
+ %{call | "gas" => integer_to_quantity(gas), "gasUsed" => integer_to_quantity(gas_used)}
end)
end
end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex
index 45b54cdd581d..f7b607e77e8c 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex
@@ -14,7 +14,7 @@ defmodule EthereumJSONRPC.HTTP do
@doc """
Sends JSONRPC request encoded as `t:iodata/0` to `url` with `options`
"""
- @callback json_rpc(url :: String.t(), json :: iodata(), options :: term()) ::
+ @callback json_rpc(url :: String.t(), json :: iodata(), headers :: [{String.t(), String.t()}], options :: term()) ::
{:ok, %{body: body :: String.t(), status_code: status_code :: pos_integer()}}
| {:error, reason :: term}
@@ -26,7 +26,7 @@ defmodule EthereumJSONRPC.HTTP do
url = url(options, method)
http_options = Keyword.fetch!(options, :http_options)
- with {:ok, %{body: body, status_code: code}} <- http.json_rpc(url, json, http_options),
+ with {:ok, %{body: body, status_code: code}} <- http.json_rpc(url, json, headers(), http_options),
{:ok, json} <- decode_json(request: [url: url, body: json], response: [status_code: code, body: body]),
{:ok, response} <- handle_response(json, code) do
{:ok, response}
@@ -38,6 +38,10 @@ defmodule EthereumJSONRPC.HTTP do
end
end
+ def json_rpc([batch | _] = chunked_batch_request, options) when is_list(batch) do
+ chunked_json_rpc(chunked_batch_request, options, [])
+ end
+
def json_rpc(batch_request, options) when is_list(batch_request) do
chunked_json_rpc([batch_request], options, [])
end
@@ -66,7 +70,7 @@ defmodule EthereumJSONRPC.HTTP do
json = encode_json(batch)
- case http.json_rpc(url, json, http_options) do
+ case http.json_rpc(url, json, headers(), http_options) do
{:ok, %{status_code: status_code} = response} when status_code in [413, 504] ->
rechunk_json_rpc(chunks, options, response, decoded_response_bodies)
@@ -90,10 +94,21 @@ defmodule EthereumJSONRPC.HTTP do
case length(batch) do
# it can't be made any smaller
1 ->
+ old_truncate = Application.get_env(:logger, :truncate)
+ Logger.configure(truncate: :infinity)
+
Logger.error(fn ->
- "413 Request Entity Too Large returned from single request batch. Cannot shrink batch further."
+ [
+ "413 Request Entity Too Large returned from single request batch. Cannot shrink batch further. ",
+ "The actual batched request was ",
+ "#{inspect(batch)}. ",
+ "The actual response of the method was ",
+ "#{inspect(response)}."
+ ]
end)
+ Logger.configure(truncate: old_truncate)
+
{:error, response}
batch_size ->
@@ -203,4 +218,8 @@ defmodule EthereumJSONRPC.HTTP do
ArgumentError ->
:error
end
+
+ defp headers do
+ Application.get_env(:ethereum_jsonrpc, __MODULE__)[:headers]
+ end
end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http/httpoison.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http/httpoison.ex
index c3e466419eee..d7f561075192 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http/httpoison.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http/httpoison.ex
@@ -8,8 +8,8 @@ defmodule EthereumJSONRPC.HTTP.HTTPoison do
@behaviour HTTP
@impl HTTP
- def json_rpc(url, json, options) when is_binary(url) and is_list(options) do
- case HTTPoison.post(url, json, [{"Content-Type", "application/json"}], options) do
+ def json_rpc(url, json, headers, options) when is_binary(url) and is_list(options) do
+ case HTTPoison.post(url, json, headers, options) do
{:ok, %HTTPoison.Response{body: body, status_code: status_code}} ->
{:ok, %{body: body, status_code: status_code}}
@@ -18,5 +18,5 @@ defmodule EthereumJSONRPC.HTTP.HTTPoison do
end
end
- def json_rpc(url, _json, _options) when is_nil(url), do: {:error, "URL is nil"}
+ def json_rpc(url, _json, _headers, _options) when is_nil(url), do: {:error, "URL is nil"}
end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex
index ecda58364425..f3c6a88662c5 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex
@@ -33,8 +33,7 @@ defmodule EthereumJSONRPC.Log do
...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"],
...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
...> "transactionIndex" => 0,
- ...> "transactionLogIndex" => 0,
- ...> "type" => "mined"
+ ...> "transactionLogIndex" => 0
...> }
...> )
%{
@@ -47,12 +46,9 @@ defmodule EthereumJSONRPC.Log do
index: 0,
second_topic: nil,
third_topic: nil,
- transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
- type: "mined"
+ transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5"
}
- Geth does not supply a `"type"`
-
iex> EthereumJSONRPC.Log.elixir_to_params(
...> %{
...> "address" => "0xda8b3276cde6d768a44b9dac659faa339a41ac55",
@@ -82,17 +78,15 @@ defmodule EthereumJSONRPC.Log do
}
"""
- def elixir_to_params(
- %{
- "address" => address_hash,
- "blockNumber" => block_number,
- "blockHash" => block_hash,
- "data" => data,
- "logIndex" => index,
- "topics" => topics,
- "transactionHash" => transaction_hash
- } = elixir
- ) do
+ def elixir_to_params(%{
+ "address" => address_hash,
+ "blockNumber" => block_number,
+ "blockHash" => block_hash,
+ "data" => data,
+ "logIndex" => index,
+ "topics" => topics,
+ "transactionHash" => transaction_hash
+ }) do
%{
address_hash: address_hash,
block_number: block_number,
@@ -102,7 +96,6 @@ defmodule EthereumJSONRPC.Log do
transaction_hash: transaction_hash
}
|> put_topics(topics)
- |> put_type(elixir)
end
@doc """
@@ -118,8 +111,7 @@ defmodule EthereumJSONRPC.Log do
...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"],
...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
...> "transactionIndex" => "0x0",
- ...> "transactionLogIndex" => "0x0",
- ...> "type" => "mined"
+ ...> "transactionLogIndex" => "0x0"
...> }
...> )
%{
@@ -131,8 +123,7 @@ defmodule EthereumJSONRPC.Log do
"topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"],
"transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
"transactionIndex" => 0,
- "transactionLogIndex" => 0,
- "type" => "mined"
+ "transactionLogIndex" => 0
}
Geth includes a `"removed"` key
@@ -172,7 +163,7 @@ defmodule EthereumJSONRPC.Log do
end
defp entry_to_elixir({key, _} = entry)
- when key in ~w(address blockHash data removed topics transactionHash type timestamp),
+ when key in ~w(address blockHash data removed topics transactionHash timestamp),
do: entry
defp entry_to_elixir({key, quantity}) when key in ~w(blockNumber logIndex transactionIndex transactionLogIndex) do
@@ -190,10 +181,4 @@ defmodule EthereumJSONRPC.Log do
|> Map.put(:third_topic, Enum.at(topics, 2))
|> Map.put(:fourth_topic, Enum.at(topics, 3))
end
-
- defp put_type(params, %{"type" => type}) do
- Map.put(params, :type, type)
- end
-
- defp put_type(params, _), do: params
end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex
index 62181ef23ff5..08744cd2cd39 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex
@@ -300,7 +300,7 @@ defmodule EthereumJSONRPC.Receipt do
end
# Arbitrum fields
- defp entry_to_elixir({key, _}) when key in ~w(returnData returnCode feeStats l1BlockNumber) do
+ defp entry_to_elixir({key, _}) when key in ~w(returnData returnCode feeStats l1BlockNumber gasUsedForL1) do
:ignore
end
@@ -314,6 +314,16 @@ defmodule EthereumJSONRPC.Receipt do
:ignore
end
+ # Optimism specific transaction receipt fields
+ defp entry_to_elixir({key, _}) when key in ~w(depositNonce depositReceiptVersion) do
+ :ignore
+ end
+
+ # EIP-4844 transaction receipt fields
+ defp entry_to_elixir({key, _}) when key in ~w(blobGasUsed blobGasPrice) do
+ :ignore
+ end
+
defp entry_to_elixir({key, value}) do
{:error, {:unknown_key, %{key: key, value: value}}}
end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex
index 8e0cffa7f2b1..37cc0a963c38 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex
@@ -32,8 +32,7 @@ defmodule EthereumJSONRPC.Receipts do
...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"],
...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
...> "transactionIndex" => 0,
- ...> "transactionLogIndex" => 0,
- ...> "type" => "mined"
+ ...> "transactionLogIndex" => 0
...> }
...> ],
...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
@@ -53,8 +52,7 @@ defmodule EthereumJSONRPC.Receipts do
"topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"],
"transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
"transactionIndex" => 0,
- "transactionLogIndex" => 0,
- "type" => "mined"
+ "transactionLogIndex" => 0
}
]
@@ -84,8 +82,7 @@ defmodule EthereumJSONRPC.Receipts do
...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"],
...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
...> "transactionIndex" => 0,
- ...> "transactionLogIndex" => 0,
- ...> "type" => "mined"
+ ...> "transactionLogIndex" => 0
...> }
...> ],
...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
@@ -165,8 +162,7 @@ defmodule EthereumJSONRPC.Receipts do
...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"],
...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
...> "transactionIndex" => "0x0",
- ...> "transactionLogIndex" => "0x0",
- ...> "type" => "mined"
+ ...> "transactionLogIndex" => "0x0"
...> }
...> ],
...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
@@ -193,8 +189,7 @@ defmodule EthereumJSONRPC.Receipts do
"topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"],
"transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
"transactionIndex" => 0,
- "transactionLogIndex" => 0,
- "type" => "mined"
+ "transactionLogIndex" => 0
}
],
"logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex
index a80db36feb5f..77ee7d3906bd 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex
@@ -58,7 +58,7 @@ defmodule EthereumJSONRPC.RequestCoordinator do
alias EthereumJSONRPC.{RollingWindow, Tracer, Transport}
- @error_key :throttleable_error_count
+ @error_key_base "throttleable_error_count"
@throttle_key :throttle_requests_count
@doc """
@@ -73,7 +73,9 @@ defmodule EthereumJSONRPC.RequestCoordinator do
@spec perform(Transport.batch_request(), Transport.t(), Transport.options(), non_neg_integer()) ::
{:ok, Transport.batch_response()} | {:error, term()}
def perform(request, transport, transport_options, throttle_timeout) do
- sleep_time = sleep_time()
+ request_method = request_method(request)
+
+ sleep_time = sleep_time(request_method)
if sleep_time <= throttle_timeout do
:timer.sleep(sleep_time)
@@ -85,7 +87,7 @@ defmodule EthereumJSONRPC.RequestCoordinator do
trace_request(request, fn ->
request
|> transport.json_rpc(transport_options)
- |> handle_transport_response()
+ |> handle_transport_response(request_method)
end)
:error ->
@@ -110,19 +112,24 @@ defmodule EthereumJSONRPC.RequestCoordinator do
defp trace_request(_, fun), do: fun.()
- defp handle_transport_response({:error, {error_type, _}} = error) when error_type in [:bad_gateway, :bad_response] do
- RollingWindow.inc(table(), @error_key)
+ defp request_method([request | _]), do: request_method(request)
+ defp request_method(%{method: method}), do: method
+ defp request_method(_), do: nil
+
+ defp handle_transport_response({:error, {error_type, _}} = error, method)
+ when error_type in [:bad_gateway, :bad_response] do
+ RollingWindow.inc(table(), method_error_key(method))
inc_throttle_table()
error
end
- defp handle_transport_response({:error, :timeout} = error) do
- RollingWindow.inc(table(), @error_key)
+ defp handle_transport_response({:error, :timeout} = error, method) do
+ RollingWindow.inc(table(), method_error_key(method))
inc_throttle_table()
error
end
- defp handle_transport_response(response) do
+ defp handle_transport_response(response, _method) do
inc_throttle_table()
response
end
@@ -154,14 +161,16 @@ defmodule EthereumJSONRPC.RequestCoordinator do
end
end
- defp sleep_time do
- wait_coefficient = RollingWindow.count(table(), @error_key)
+ defp sleep_time(request_method) do
+ wait_coefficient = RollingWindow.count(table(), method_error_key(request_method))
jitter = :rand.uniform(config!(:max_jitter))
wait_per_timeout = config!(:wait_per_timeout)
wait_coefficient * (wait_per_timeout + jitter)
end
+ defp method_error_key(method), do: :"#{@error_key_base}_#{method}"
+
defp table do
:rolling_window_opts
|> config!()
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/rolling_window.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/rolling_window.ex
index 0b2c6b959462..65657e7b92c5 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/rolling_window.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/rolling_window.ex
@@ -21,8 +21,6 @@ defmodule EthereumJSONRPC.RollingWindow do
use GenServer
- require Logger
-
def child_spec([init_arguments]) do
child_spec([init_arguments, []])
end
diff --git a/apps/ethereum_jsonrpc/lib/rsk.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/rsk.ex
similarity index 50%
rename from apps/ethereum_jsonrpc/lib/rsk.ex
rename to apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/rsk.ex
index 85d8267d0438..e0d1eeda0fce 100644
--- a/apps/ethereum_jsonrpc/lib/rsk.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/rsk.ex
@@ -3,11 +3,19 @@ defmodule EthereumJSONRPC.RSK do
Ethereum JSONRPC methods that are/are not supported by [RSK](https://www.rsk.co/).
"""
+ alias EthereumJSONRPC.RSK.Traces
+ alias EthereumJSONRPC.TraceBlock
+
@behaviour EthereumJSONRPC.Variant
- def fetch_internal_transactions(_, _), do: :ignore
+ def fetch_internal_transactions(_transaction_params, _json_rpc_named_arguments), do: :ignore
+
def fetch_pending_transactions(_), do: :ignore
- def fetch_block_internal_transactions(_block_numbers, _json_rpc_named_arguments), do: :ignore
+
+ def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) do
+ TraceBlock.fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments, Traces)
+ end
+
def fetch_beneficiaries(_, _), do: :ignore
def fetch_first_trace(_transactions_params, _json_rpc_named_arguments), do: :ignore
end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/rsk/trace.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/rsk/trace.ex
new file mode 100644
index 000000000000..3a765132493e
--- /dev/null
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/rsk/trace.ex
@@ -0,0 +1,21 @@
+defmodule EthereumJSONRPC.RSK.Trace do
+ @moduledoc """
+ Trace returned by
+ [`trace_block`](https://dev.rootstock.io/rsk/node/architecture/json-rpc/#json-rpc-supported).
+ """
+
+ alias EthereumJSONRPC.Besu.Trace, as: BesuTrace
+ alias EthereumJSONRPC.Nethermind.Trace, as: NethermindTrace
+
+ def elixir_to_params(elixir) do
+ NethermindTrace.elixir_to_params(elixir)
+ end
+
+ def to_elixir(trace) do
+ {transaction_index, trace_no_tp} = Map.pop(trace, "transactionPosition")
+
+ trace_no_tp
+ |> Map.put("transactionIndex", transaction_index)
+ |> BesuTrace.to_elixir()
+ end
+end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/rsk/traces.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/rsk/traces.ex
new file mode 100644
index 000000000000..f74eef343c58
--- /dev/null
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/rsk/traces.ex
@@ -0,0 +1,16 @@
+defmodule EthereumJSONRPC.RSK.Traces do
+ @moduledoc """
+ Traces returned by
+ [`trace_block`](https://dev.rootstock.io/rsk/node/architecture/json-rpc/#json-rpc-supported).
+ """
+
+ alias EthereumJSONRPC.RSK.Trace
+
+ def elixir_to_params(elixir) when is_list(elixir) do
+ Enum.map(elixir, &Trace.elixir_to_params/1)
+ end
+
+ def to_elixir(traces) when is_list(traces) do
+ Enum.map(traces, &Trace.to_elixir/1)
+ end
+end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/trace_block.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/trace_block.ex
new file mode 100644
index 000000000000..2192962c0ffb
--- /dev/null
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/trace_block.ex
@@ -0,0 +1,91 @@
+defmodule EthereumJSONRPC.TraceBlock do
+ @moduledoc """
+ Functions for processing the data from `trace_block` JSON RPC method.
+ """
+
+ import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2, request: 1]
+
+ @spec fetch_block_internal_transactions(
+ [non_neg_integer()],
+ EthereumJSONRPC.json_rpc_named_arguments(),
+ EthereumJSONRPC.RSK.Traces
+ ) :: {:error, any} | {:ok, any}
+ def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments, traces_module) do
+ id_to_params = id_to_params(block_numbers)
+
+ with {:ok, responses} <-
+ id_to_params
+ |> trace_block_requests()
+ |> json_rpc(json_rpc_named_arguments) do
+ trace_block_responses_to_internal_transactions_params(responses, id_to_params, traces_module)
+ end
+ end
+
+ defp trace_block_requests(id_to_params) when is_map(id_to_params) do
+ Enum.map(id_to_params, fn {id, block_number} ->
+ trace_block_request(%{id: id, block_number: block_number})
+ end)
+ end
+
+ defp trace_block_request(%{id: id, block_number: block_number}) do
+ request(%{id: id, method: "trace_block", params: [integer_to_quantity(block_number)]})
+ end
+
+ defp trace_block_responses_to_internal_transactions_params(responses, id_to_params, traces_module) do
+ with {:ok, traces} <- trace_block_responses_to_traces(responses, id_to_params) do
+ params =
+ traces
+ |> traces_module.to_elixir()
+ |> traces_module.elixir_to_params()
+
+ {:ok, params}
+ end
+ end
+
+ defp trace_block_responses_to_traces(responses, id_to_params) do
+ responses
+ |> EthereumJSONRPC.sanitize_responses(id_to_params)
+ |> Enum.map(&trace_block_response_to_traces(&1, id_to_params))
+ |> Enum.group_by(&elem(&1, 0), &elem(&1, 1))
+ |> case do
+ %{error: reasons} ->
+ {:error, reasons}
+
+ %{ok: traces_list} ->
+ traces =
+ traces_list
+ |> List.flatten()
+
+ {:ok, traces}
+
+ %{} ->
+ {:ok, []}
+ end
+ end
+
+ defp trace_block_response_to_traces(%{result: results}, _id_to_params)
+ when is_list(results) do
+ annotated_traces =
+ results
+ |> Enum.scan(%{"index" => -1}, fn trace, %{"index" => internal_transaction_index} ->
+ internal_transaction_index = if trace["traceAddress"] == [], do: 0, else: internal_transaction_index + 1
+
+ trace
+ |> Map.put("index", internal_transaction_index)
+ end)
+
+ {:ok, annotated_traces}
+ end
+
+ defp trace_block_response_to_traces(%{id: id, error: error}, id_to_params)
+ when is_map(id_to_params) do
+ block_number = Map.fetch!(id_to_params, id)
+
+ annotated_error =
+ Map.put(error, :data, %{
+ "blockNumber" => block_number
+ })
+
+ {:error, annotated_error}
+ end
+end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
index f82a94c8d98c..2f11da852095 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
@@ -7,8 +7,6 @@ defmodule EthereumJSONRPC.Transaction do
[`eth_getTransactionByBlockHashAndIndex`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionbyblockhashandindex),
and [`eth_getTransactionByBlockNumberAndIndex`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionbyblocknumberandindex)
"""
- require Logger
-
import EthereumJSONRPC, only: [quantity_to_integer: 1, integer_to_quantity: 1, request: 1]
alias EthereumJSONRPC
@@ -44,6 +42,8 @@ defmodule EthereumJSONRPC.Transaction do
* `"maxPriorityFeePerGas"` - `t:EthereumJSONRPC.quantity/0` of wei to denote max priority fee per unit of gas used. Introduced in [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md)
* `"maxFeePerGas"` - `t:EthereumJSONRPC.quantity/0` of wei to denote max fee per unit of gas used. Introduced in [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md)
* `"type"` - `t:EthereumJSONRPC.quantity/0` denotes transaction type. Introduced in [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md)
+ * `"executionNode"` - `t:EthereumJSONRPC.address/0` of execution node (used by Suave).
+ * `"requestRecord"` - map of wrapped transaction data (used by Suave).
"""
@type t :: %{
String.t() =>
@@ -68,7 +68,21 @@ defmodule EthereumJSONRPC.Transaction do
transaction_index: non_neg_integer(),
max_priority_fee_per_gas: non_neg_integer(),
max_fee_per_gas: non_neg_integer(),
- type: non_neg_integer()
+ type: non_neg_integer(),
+ execution_node_hash: EthereumJSONRPC.address(),
+ wrapped_type: non_neg_integer(),
+ wrapped_nonce: non_neg_integer(),
+ wrapped_to_address_hash: EthereumJSONRPC.address(),
+ wrapped_gas: non_neg_integer(),
+ wrapped_gas_price: non_neg_integer(),
+ wrapped_max_priority_fee_per_gas: non_neg_integer(),
+ wrapped_max_fee_per_gas: non_neg_integer(),
+ wrapped_value: non_neg_integer(),
+ wrapped_input: String.t(),
+ wrapped_v: non_neg_integer(),
+ wrapped_r: non_neg_integer(),
+ wrapped_s: non_neg_integer(),
+ wrapped_hash: EthereumJSONRPC.hash()
}
@doc """
@@ -135,6 +149,7 @@ defmodule EthereumJSONRPC.Transaction do
%{
block_hash: nil,
block_number: nil,
+ gas_price: nil,
from_address_hash: "0x870006d72c247bc1e90983c71b3234ee01d3c9d9",
gas: 182154,
hash: "0x8d2cd1fae48ea0d2a20bb74abbfca05c2d805793e1b42fa844bbdd90f2512f39",
@@ -154,6 +169,80 @@ defmodule EthereumJSONRPC.Transaction do
"""
@spec elixir_to_params(elixir) :: params
+ # this is for Suave chain (handles `executionNode` and `requestRecord` fields along with EIP-1559 fields)
+ def elixir_to_params(
+ %{
+ "blockHash" => block_hash,
+ "blockNumber" => block_number,
+ "from" => from_address_hash,
+ "gas" => gas,
+ "gasPrice" => gas_price,
+ "hash" => hash,
+ "input" => input,
+ "nonce" => nonce,
+ "r" => r,
+ "s" => s,
+ "to" => to_address_hash,
+ "transactionIndex" => index,
+ "v" => v,
+ "value" => value,
+ "type" => type,
+ "maxPriorityFeePerGas" => max_priority_fee_per_gas,
+ "maxFeePerGas" => max_fee_per_gas,
+ "executionNode" => execution_node_hash,
+ "requestRecord" => wrapped
+ } = transaction
+ ) do
+ result = %{
+ block_hash: block_hash,
+ block_number: block_number,
+ from_address_hash: from_address_hash,
+ gas: gas,
+ gas_price: gas_price,
+ hash: hash,
+ index: index,
+ input: input,
+ nonce: nonce,
+ r: r,
+ s: s,
+ to_address_hash: to_address_hash,
+ v: v,
+ value: value,
+ transaction_index: index,
+ type: type,
+ max_priority_fee_per_gas: max_priority_fee_per_gas,
+ max_fee_per_gas: max_fee_per_gas
+ }
+
+ # credo:disable-for-next-line
+ result =
+ if Application.get_env(:explorer, :chain_type) == "suave" do
+ Map.merge(result, %{
+ execution_node_hash: execution_node_hash,
+ wrapped_type: quantity_to_integer(Map.get(wrapped, "type")),
+ wrapped_nonce: quantity_to_integer(Map.get(wrapped, "nonce")),
+ wrapped_to_address_hash: Map.get(wrapped, "to"),
+ wrapped_gas: quantity_to_integer(Map.get(wrapped, "gas")),
+ wrapped_gas_price: quantity_to_integer(Map.get(wrapped, "gasPrice")),
+ wrapped_max_priority_fee_per_gas: quantity_to_integer(Map.get(wrapped, "maxPriorityFeePerGas")),
+ wrapped_max_fee_per_gas: quantity_to_integer(Map.get(wrapped, "maxFeePerGas")),
+ wrapped_value: quantity_to_integer(Map.get(wrapped, "value")),
+ wrapped_input: Map.get(wrapped, "input"),
+ wrapped_v: quantity_to_integer(Map.get(wrapped, "v")),
+ wrapped_r: quantity_to_integer(Map.get(wrapped, "r")),
+ wrapped_s: quantity_to_integer(Map.get(wrapped, "s")),
+ wrapped_hash: Map.get(wrapped, "hash")
+ })
+ else
+ result
+ end
+
+ put_if_present(transaction, result, [
+ {"creates", :created_contract_address_hash},
+ {"block_timestamp", :block_timestamp}
+ ])
+ end
+
def elixir_to_params(
%{
"blockHash" => block_hash,
@@ -196,11 +285,10 @@ defmodule EthereumJSONRPC.Transaction do
max_fee_per_gas: max_fee_per_gas
}
- if transaction["creates"] do
- Map.put(result, :created_contract_address_hash, transaction["creates"])
- else
- result
- end
+ put_if_present(transaction, result, [
+ {"creates", :created_contract_address_hash},
+ {"block_timestamp", :block_timestamp}
+ ])
end
# txpool_content method on Erigon node returns tx data
@@ -230,6 +318,7 @@ defmodule EthereumJSONRPC.Transaction do
block_number: block_number,
from_address_hash: from_address_hash,
gas: gas,
+ gas_price: nil,
hash: hash,
index: index,
input: input,
@@ -245,11 +334,80 @@ defmodule EthereumJSONRPC.Transaction do
max_fee_per_gas: max_fee_per_gas
}
- if transaction["creates"] do
- Map.put(result, :created_contract_address_hash, transaction["creates"])
- else
- result
- end
+ put_if_present(transaction, result, [
+ {"creates", :created_contract_address_hash},
+ {"block_timestamp", :block_timestamp}
+ ])
+ end
+
+ # this is for Suave chain (handles `executionNode` and `requestRecord` fields without EIP-1559 fields)
+ def elixir_to_params(
+ %{
+ "blockHash" => block_hash,
+ "blockNumber" => block_number,
+ "from" => from_address_hash,
+ "gas" => gas,
+ "gasPrice" => gas_price,
+ "hash" => hash,
+ "input" => input,
+ "nonce" => nonce,
+ "r" => r,
+ "s" => s,
+ "to" => to_address_hash,
+ "transactionIndex" => index,
+ "v" => v,
+ "value" => value,
+ "type" => type,
+ "executionNode" => execution_node_hash,
+ "requestRecord" => wrapped
+ } = transaction
+ ) do
+ result = %{
+ block_hash: block_hash,
+ block_number: block_number,
+ from_address_hash: from_address_hash,
+ gas: gas,
+ gas_price: gas_price,
+ hash: hash,
+ index: index,
+ input: input,
+ nonce: nonce,
+ r: r,
+ s: s,
+ to_address_hash: to_address_hash,
+ v: v,
+ value: value,
+ transaction_index: index,
+ type: type
+ }
+
+ # credo:disable-for-next-line
+ result =
+ if Application.get_env(:explorer, :chain_type) == "suave" do
+ Map.merge(result, %{
+ execution_node_hash: execution_node_hash,
+ wrapped_type: quantity_to_integer(Map.get(wrapped, "type")),
+ wrapped_nonce: quantity_to_integer(Map.get(wrapped, "nonce")),
+ wrapped_to_address_hash: Map.get(wrapped, "to"),
+ wrapped_gas: quantity_to_integer(Map.get(wrapped, "gas")),
+ wrapped_gas_price: quantity_to_integer(Map.get(wrapped, "gasPrice")),
+ wrapped_max_priority_fee_per_gas: quantity_to_integer(Map.get(wrapped, "maxPriorityFeePerGas")),
+ wrapped_max_fee_per_gas: quantity_to_integer(Map.get(wrapped, "maxFeePerGas")),
+ wrapped_value: quantity_to_integer(Map.get(wrapped, "value")),
+ wrapped_input: Map.get(wrapped, "input"),
+ wrapped_v: quantity_to_integer(Map.get(wrapped, "v")),
+ wrapped_r: quantity_to_integer(Map.get(wrapped, "r")),
+ wrapped_s: quantity_to_integer(Map.get(wrapped, "s")),
+ wrapped_hash: Map.get(wrapped, "hash")
+ })
+ else
+ result
+ end
+
+ put_if_present(transaction, result, [
+ {"creates", :created_contract_address_hash},
+ {"block_timestamp", :block_timestamp}
+ ])
end
def elixir_to_params(
@@ -290,11 +448,10 @@ defmodule EthereumJSONRPC.Transaction do
type: type
}
- if transaction["creates"] do
- Map.put(result, :created_contract_address_hash, transaction["creates"])
- else
- result
- end
+ put_if_present(transaction, result, [
+ {"creates", :created_contract_address_hash},
+ {"block_timestamp", :block_timestamp}
+ ])
end
def elixir_to_params(
@@ -333,11 +490,10 @@ defmodule EthereumJSONRPC.Transaction do
transaction_index: index
}
- if transaction["creates"] do
- Map.put(result, :created_contract_address_hash, transaction["creates"])
- else
- result
- end
+ put_if_present(transaction, result, [
+ {"creates", :created_contract_address_hash},
+ {"block_timestamp", :block_timestamp}
+ ])
end
@doc """
@@ -418,13 +574,14 @@ defmodule EthereumJSONRPC.Transaction do
}
"""
- def to_elixir(transaction) when is_map(transaction) do
- Enum.into(transaction, %{}, &entry_to_elixir/1)
- end
+ def to_elixir(transaction, block_timestamp \\ nil)
- def to_elixir(transaction) when is_binary(transaction) do
- # Logger.warn(["Fetched transaction is not full: ", transaction])
+ def to_elixir(transaction, block_timestamp) when is_map(transaction) do
+ initial = (block_timestamp && %{"block_timestamp" => block_timestamp}) || %{}
+ Enum.into(transaction, initial, &entry_to_elixir/1)
+ end
+ def to_elixir(transaction, _block_timestamp) when is_binary(transaction) do
nil
end
@@ -448,7 +605,7 @@ defmodule EthereumJSONRPC.Transaction do
#
# "txType": to avoid FunctionClauseError when indexing Wanchain
defp entry_to_elixir({key, value})
- when key in ~w(blockHash condition creates from hash input jsonrpc publicKey raw to txType),
+ when key in ~w(blockHash condition creates from hash input jsonrpc publicKey raw to txType executionNode requestRecord),
do: {key, value}
# specific to Nethermind client
@@ -461,6 +618,16 @@ defmodule EthereumJSONRPC.Transaction do
{key, quantity_to_integer(quantity)}
end
+ # to be merged with the clause above ^
+ defp entry_to_elixir({"maxFeePerBlobGas", _value}) do
+ {nil, nil}
+ end
+
+ # EIP-4844 specific field with value of type of list of hashes
+ defp entry_to_elixir({"blobVersionedHashes", _value}) do
+ {nil, nil}
+ end
+
# as always ganache has it's own vision on JSON RPC standard
defp entry_to_elixir({key, nil}) when key in ~w(r s v) do
{key, 0}
@@ -488,4 +655,16 @@ defmodule EthereumJSONRPC.Transaction do
defp entry_to_elixir(_) do
{nil, nil}
end
+
+ defp put_if_present(transaction, result, keys) do
+ Enum.reduce(keys, result, fn {from_key, to_key}, acc ->
+ value = transaction[from_key]
+
+ if value do
+ Map.put(acc, to_key, value)
+ else
+ acc
+ end
+ end)
+ end
end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transactions.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transactions.ex
index 9b3937873932..ecdf103b4e89 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transactions.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transactions.ex
@@ -151,9 +151,9 @@ defmodule EthereumJSONRPC.Transactions do
]
"""
- def to_elixir(transactions) when is_list(transactions) do
+ def to_elixir(transactions, block_timestamp \\ nil) when is_list(transactions) do
transactions
- |> Enum.map(&Transaction.to_elixir/1)
+ |> Enum.map(&Transaction.to_elixir(&1, block_timestamp))
|> Enum.filter(&(!is_nil(&1)))
end
end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex
new file mode 100644
index 000000000000..f7220b044d0b
--- /dev/null
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex
@@ -0,0 +1,116 @@
+# credo:disable-for-this-file
+defmodule EthereumJSONRPC.Utility.RangesHelper do
+ @moduledoc """
+ Helper for ranges manipulations.
+ """
+
+ @default_trace_block_ranges "0..latest"
+
+ @spec traceable_block_number?(integer() | nil) :: boolean()
+ def traceable_block_number?(block_number) do
+ if trace_ranges_present?() do
+ number_in_ranges?(block_number, get_trace_block_ranges())
+ else
+ true
+ end
+ end
+
+ @spec filter_traceable_block_numbers([integer()]) :: [integer()]
+ def filter_traceable_block_numbers(block_numbers) do
+ if trace_ranges_present?() do
+ trace_block_ranges = get_trace_block_ranges()
+ Enum.filter(block_numbers, &number_in_ranges?(&1, trace_block_ranges))
+ else
+ block_numbers
+ end
+ end
+
+ @spec trace_ranges_present? :: boolean()
+ def trace_ranges_present? do
+ Application.get_env(:indexer, :trace_block_ranges) != @default_trace_block_ranges
+ end
+
+ @spec get_trace_block_ranges :: [Range.t() | integer()]
+ def get_trace_block_ranges do
+ :indexer
+ |> Application.get_env(:trace_block_ranges)
+ |> parse_block_ranges()
+ end
+
+ @spec parse_block_ranges(binary()) :: [Range.t() | integer()]
+ def parse_block_ranges(block_ranges_string) do
+ block_ranges_string
+ |> String.split(",")
+ |> Enum.map(fn string_range ->
+ case String.split(string_range, "..") do
+ [from_string, "latest"] ->
+ parse_integer(from_string)
+
+ [from_string, to_string] ->
+ get_from_to(from_string, to_string)
+
+ _ ->
+ nil
+ end
+ end)
+ |> sanitize_ranges()
+ end
+
+ defp number_in_ranges?(number, ranges) do
+ Enum.reduce_while(ranges, false, fn
+ _from.._to = range, _acc -> if number in range, do: {:halt, true}, else: {:cont, false}
+ num_to_latest, _acc -> if number >= num_to_latest, do: {:halt, true}, else: {:cont, false}
+ end)
+ end
+
+ defp get_from_to(from_string, to_string) do
+ with {from, ""} <- Integer.parse(from_string),
+ {to, ""} <- Integer.parse(to_string) do
+ if from <= to, do: from..to, else: nil
+ else
+ _ -> nil
+ end
+ end
+
+ @spec sanitize_ranges([Range.t() | integer()]) :: [Range.t() | integer()]
+ def sanitize_ranges(ranges) do
+ ranges
+ |> Enum.reject(&is_nil/1)
+ |> Enum.sort_by(
+ fn
+ from.._to -> from
+ el -> el
+ end,
+ :asc
+ )
+ |> Enum.chunk_while(
+ nil,
+ fn
+ _from.._to = chunk, nil ->
+ {:cont, chunk}
+
+ _ch_from..ch_to = chunk, acc_from..acc_to = acc ->
+ if Range.disjoint?(chunk, acc),
+ do: {:cont, acc, chunk},
+ else: {:cont, acc_from..max(ch_to, acc_to)}
+
+ num, nil ->
+ {:halt, num}
+
+ num, acc_from.._ = acc ->
+ if Range.disjoint?(num..num, acc), do: {:cont, acc, num}, else: {:halt, acc_from}
+
+ _, num ->
+ {:halt, num}
+ end,
+ fn remainder -> {:cont, remainder, nil} end
+ )
+ end
+
+ defp parse_integer(string) do
+ case Integer.parse(string) do
+ {number, ""} -> number
+ _ -> nil
+ end
+ end
+end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/withdrawal.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/withdrawal.ex
index 6c86c73d4344..3763fc5d66c0 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/withdrawal.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/withdrawal.ex
@@ -45,7 +45,7 @@ defmodule EthereumJSONRPC.Withdrawal do
...> )
%{
address_hash: "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
- amount: 4040000000000,
+ amount: 4040000000000000000000,
block_hash: "0x7f035c5f3c0678250853a1fde6027def7cac1812667bd0d5ab7ccb94eb8b6f3a",
block_number: 3,
index: 3867,
@@ -67,7 +67,7 @@ defmodule EthereumJSONRPC.Withdrawal do
address_hash: address_hash,
block_hash: block_hash,
block_number: block_number,
- amount: amount
+ amount: amount * 1_000_000_000
}
end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/withdrawals.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/withdrawals.ex
index 1c520b9f9a44..863857eee573 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/withdrawals.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/withdrawals.ex
@@ -26,7 +26,7 @@ defmodule EthereumJSONRPC.Withdrawals do
[
%{
address_hash: "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
- amount: 4040000000000,
+ amount: 4040000000000000000000,
block_hash: "0x7f035c5f3c0678250853a1fde6027def7cac1812667bd0d5ab7ccb94eb8b6f3a",
index: 3867,
validator_index: 1721,
diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs
index 8e5cf139a199..2446c3e2e28b 100644
--- a/apps/ethereum_jsonrpc/mix.exs
+++ b/apps/ethereum_jsonrpc/mix.exs
@@ -11,7 +11,7 @@ defmodule EthereumJsonrpc.MixProject do
deps_path: "../../deps",
description: "Ethereum JSONRPC client.",
dialyzer: [
- plt_add_deps: :transitive,
+ plt_add_deps: :app_tree,
plt_add_apps: [:mix],
ignore_warnings: "../../.dialyzer-ignore"
],
@@ -23,7 +23,7 @@ defmodule EthereumJsonrpc.MixProject do
dialyzer: :test
],
start_permanent: Mix.env() == :prod,
- version: "5.1.5"
+ version: "6.0.0"
]
end
@@ -85,7 +85,8 @@ defmodule EthereumJsonrpc.MixProject do
{:decimal, "~> 2.0"},
{:decorator, "~> 1.4"},
{:hackney, "~> 1.18"},
- {:poolboy, "~> 1.5.2"}
+ {:poolboy, "~> 1.5.2"},
+ {:logger_json, "~> 5.1"}
]
end
end
diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs
index 4f59274e8533..f8d4ecd00172 100644
--- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs
+++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs
@@ -32,29 +32,44 @@ defmodule EthereumJSONRPC.BlockTest do
"uncles" => []
})
- assert result == %{
- difficulty: 17_561_410_778,
- extra_data: "0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32",
- gas_limit: 5000,
- gas_used: 0,
- hash: "0x4d9423080290a650eaf6db19c87c76dff83d1b4ab64aefe6e5c5aa2d1f4b6623",
- logs_bloom:
- "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
- mix_hash: "0xbbb93d610b2b0296a59f18474ac3d6086a9902aa7ca4b9a306692f7c3d496fdf",
- miner_hash: "0xbb7b8287f3f0a933474a79eae42cbca977791171",
- nonce: 5_539_500_215_739_777_653,
- number: 59,
- parent_hash: "0xcd5b5c4cecd7f18a13fe974255badffd58e737dc67596d56bc01f063dd282e9e",
- receipts_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
- sha3_uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
- size: 542,
- state_root: "0x6fd0a5d82ca77d9f38c3ebbde11b11d304a5fcf3854f291df64395ab38ed43ba",
- timestamp: Timex.parse!("2015-07-30T15:32:07Z", "{ISO:Extended:Z}"),
- total_difficulty: nil,
- transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
- uncles: [],
- withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
- }
+ assert result ==
+ %{
+ difficulty: 17_561_410_778,
+ extra_data: "0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32",
+ gas_limit: 5000,
+ gas_used: 0,
+ hash: "0x4d9423080290a650eaf6db19c87c76dff83d1b4ab64aefe6e5c5aa2d1f4b6623",
+ logs_bloom:
+ "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ mix_hash: "0xbbb93d610b2b0296a59f18474ac3d6086a9902aa7ca4b9a306692f7c3d496fdf",
+ miner_hash: "0xbb7b8287f3f0a933474a79eae42cbca977791171",
+ nonce: 5_539_500_215_739_777_653,
+ number: 59,
+ parent_hash: "0xcd5b5c4cecd7f18a13fe974255badffd58e737dc67596d56bc01f063dd282e9e",
+ receipts_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
+ sha3_uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
+ size: 542,
+ state_root: "0x6fd0a5d82ca77d9f38c3ebbde11b11d304a5fcf3854f291df64395ab38ed43ba",
+ timestamp: Timex.parse!("2015-07-30T15:32:07Z", "{ISO:Extended:Z}"),
+ total_difficulty: nil,
+ transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
+ uncles: [],
+ withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
+ }
+ |> (&if(Application.get_env(:explorer, :chain_type) == "rsk",
+ do:
+ Map.merge(
+ &1,
+ %{
+ bitcoin_merged_mining_coinbase_transaction: nil,
+ bitcoin_merged_mining_header: nil,
+ bitcoin_merged_mining_merkle_proof: nil,
+ hash_for_merged_mining: nil,
+ minimum_gas_price: nil
+ }
+ ),
+ else: &1
+ )).()
end
end
diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/contract_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/contract_test.exs
index ef2f58ed4a37..76100d469b3c 100644
--- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/contract_test.exs
+++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/contract_test.exs
@@ -5,6 +5,8 @@ defmodule EthereumJSONRPC.ContractTest do
import Mox
+ setup :verify_on_exit!
+
describe "execute_contract_functions/3" do
test "executes the functions with and without the block_number, returns results in order" do
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/encoder_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/encoder_test.exs
index 38f87e08af4c..7539b8e582ba 100644
--- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/encoder_test.exs
+++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/encoder_test.exs
@@ -27,6 +27,38 @@ defmodule EthereumJSONRPC.EncoderTest do
"0x9507d39a000000000000000000000000000000000000000000000000000000000000000a"
end
+ test "generates the correct encoding with string argument" do
+ function_selector = %ABI.FunctionSelector{
+ function: "isNewsletterCoverFullyClaimed",
+ input_names: ["newsletterId"],
+ inputs_indexed: nil,
+ return_names: [""],
+ returns: [:bool],
+ state_mutability: :view,
+ type: :function,
+ types: [:string]
+ }
+
+ assert Encoder.encode_function_call(function_selector, ["6564f5623e2a9f0001cb7fee"]) ==
+ "0xa07a712d000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000183635363466353632336532613966303030316362376665650000000000000000"
+ end
+
+ test "generates the correct encoding with string started with 0x" do
+ function_selector = %ABI.FunctionSelector{
+ function: "isNewsletterCoverFullyClaimed",
+ input_names: ["newsletterId"],
+ inputs_indexed: nil,
+ return_names: [""],
+ returns: [:bool],
+ state_mutability: :view,
+ type: :function,
+ types: [:string]
+ }
+
+ assert Encoder.encode_function_call(function_selector, ["0x123"]) ==
+ "0xa07a712d000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000053078313233000000000000000000000000000000000000000000000000000000"
+ end
+
test "generates the correct encoding with addresses arguments" do
function_selector = %ABI.FunctionSelector{
function: "tokens",
diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs
index 0aa365b73325..cc22f8baf483 100644
--- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs
+++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs
@@ -5,14 +5,14 @@ defmodule EthereumJSONRPC.GethTest do
alias EthereumJSONRPC.Geth
- @moduletag :no_nethermind
+ setup :verify_on_exit!
describe "fetch_internal_transactions/2" do
# Infura Mainnet does not support debug_traceTransaction, so this cannot be tested expect in Mox
setup do
- EthereumJSONRPC.Case.Geth.Mox.setup()
initial_env = Application.get_all_env(:ethereum_jsonrpc)
on_exit(fn -> Application.put_all_env([{:ethereum_jsonrpc, initial_env}]) end)
+ EthereumJSONRPC.Case.Geth.Mox.setup()
end
setup :verify_on_exit!
@@ -24,7 +24,7 @@ defmodule EthereumJSONRPC.GethTest do
transaction_hash = "0x32b17f27ddb546eab3c4c33f31eb22c1cb992d4ccc50dae26922805b717efe5c"
tracer = File.read!("priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js")
- expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id, params: [^transaction_hash, %{tracer: ^tracer}]}], _ ->
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id, params: [^transaction_hash, %{"tracer" => ^tracer}]}], _ ->
{:ok,
[
%{
@@ -47,7 +47,7 @@ defmodule EthereumJSONRPC.GethTest do
]}
end)
- Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js")
+ Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js", debug_trace_transaction_timeout: "5s")
assert {:ok,
[
@@ -96,7 +96,7 @@ defmodule EthereumJSONRPC.GethTest do
tracer = File.read!("priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js")
expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn
- [%{id: id, params: [^transaction_hash, %{tracer: "callTracer"}]}], _ ->
+ [%{id: id, params: [^transaction_hash, %{"tracer" => "callTracer"}]}], _ ->
{:ok,
[
%{
@@ -220,7 +220,7 @@ defmodule EthereumJSONRPC.GethTest do
end)
expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn
- [%{id: id, params: [^transaction_hash, %{tracer: ^tracer}]}], _ ->
+ [%{id: id, params: [^transaction_hash, %{"tracer" => ^tracer}]}], _ ->
{:ok,
[
%{
@@ -357,9 +357,11 @@ defmodule EthereumJSONRPC.GethTest do
]}
end)
+ Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s")
+
call_tracer_internal_txs = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments)
- Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js")
+ Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js", debug_trace_transaction_timeout: "5s")
assert call_tracer_internal_txs ==
Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments)
@@ -381,7 +383,7 @@ defmodule EthereumJSONRPC.GethTest do
tracer = File.read!("priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js")
expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn
- [%{id: id, params: [^transaction_hash, %{tracer: "callTracer"}]}], _ ->
+ [%{id: id, params: [^transaction_hash, %{"tracer" => "callTracer"}]}], _ ->
{:ok,
[
%{
@@ -403,7 +405,7 @@ defmodule EthereumJSONRPC.GethTest do
end)
expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn
- [%{id: id, params: [^transaction_hash, %{tracer: ^tracer}]}], _ ->
+ [%{id: id, params: [^transaction_hash, %{"tracer" => ^tracer}]}], _ ->
{:ok,
[
%{
@@ -427,18 +429,298 @@ defmodule EthereumJSONRPC.GethTest do
]}
end)
+ Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s")
+
call_tracer_internal_txs = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments)
- Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js")
+ Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js", debug_trace_transaction_timeout: "5s")
assert call_tracer_internal_txs ==
Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments)
end
+
+ test "successfully handle single stop opcode from call_tracer", %{
+ json_rpc_named_arguments: json_rpc_named_arguments
+ } do
+ transaction_hash = "0xb342cafc6ac552c3be2090561453204c8784caf025ac8267320834e4cd163d96"
+ block_number = 3_287_375
+ transaction_index = 13
+
+ transaction_params = %{
+ block_number: block_number,
+ transaction_index: transaction_index,
+ hash_data: transaction_hash
+ }
+
+ expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn
+ [%{id: id, params: [^transaction_hash, %{"tracer" => "callTracer"}]}], _ ->
+ {:ok,
+ [
+ %{
+ id: id,
+ result: %{
+ "type" => "STOP",
+ "from" => "0x0000000000000000000000000000000000000000",
+ "value" => "0x0",
+ "gas" => "0x0",
+ "gasUsed" => "0x5842"
+ }
+ }
+ ]}
+ end)
+
+ Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s")
+
+ assert {:ok,
+ [
+ %{
+ block_number: 3_287_375,
+ error: "execution stopped",
+ from_address_hash: "0x0000000000000000000000000000000000000000",
+ input: "0x",
+ trace_address: [],
+ transaction_hash: "0xb342cafc6ac552c3be2090561453204c8784caf025ac8267320834e4cd163d96",
+ transaction_index: 13,
+ type: "stop",
+ value: 0
+ }
+ ]} = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments)
+ end
+
+ test "uppercase type parsing result is the same as lowercase", %{
+ json_rpc_named_arguments: json_rpc_named_arguments
+ } do
+ transaction_hash = "0xb342cafc6ac552c3be2090561453204c8784caf025ac8267320834e4cd163d96"
+ block_number = 3_287_375
+ transaction_index = 13
+
+ transaction_params = %{
+ block_number: block_number,
+ transaction_index: transaction_index,
+ hash_data: transaction_hash
+ }
+
+ expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn
+ [%{id: id, params: [^transaction_hash, %{"tracer" => "callTracer"}]}], _ ->
+ {:ok,
+ [
+ %{
+ id: id,
+ result: %{
+ "type" => "CREATE",
+ "from" => "0x117b358218da5a4f647072ddb50ded038ed63d17",
+ "to" => "0x205a6b72ce16736c9d87172568a9c0cb9304de0d",
+ "value" => "0x0",
+ "gas" => "0x106f5",
+ "gasUsed" => "0x106f5",
+ "input" =>
+ "0x608060405234801561001057600080fd5b50610150806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d9565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b60008135905061009781610103565b92915050565b6000602082840312156100b3576100b26100fe565b5b60006100c184828501610088565b91505092915050565b6100d3816100f4565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea26469706673582212209a159a4f3847890f10bfb87871a61eba91c5dbf5ee3cf6398207e292eee22a1664736f6c63430008070033",
+ "output" =>
+ "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d9565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b60008135905061009781610103565b92915050565b6000602082840312156100b3576100b26100fe565b5b60006100c184828501610088565b91505092915050565b6100d3816100f4565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea26469706673582212209a159a4f3847890f10bfb87871a61eba91c5dbf5ee3cf6398207e292eee22a1664736f6c63430008070033"
+ }
+ }
+ ]}
+ end)
+
+ Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s")
+
+ uppercase_result = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments)
+
+ expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn
+ [%{id: id, params: [^transaction_hash, %{"tracer" => "callTracer"}]}], _ ->
+ {:ok,
+ [
+ %{
+ id: id,
+ result: %{
+ "type" => "create",
+ "from" => "0x117b358218da5a4f647072ddb50ded038ed63d17",
+ "to" => "0x205a6b72ce16736c9d87172568a9c0cb9304de0d",
+ "value" => "0x0",
+ "gas" => "0x106f5",
+ "gasUsed" => "0x106f5",
+ "input" =>
+ "0x608060405234801561001057600080fd5b50610150806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d9565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b60008135905061009781610103565b92915050565b6000602082840312156100b3576100b26100fe565b5b60006100c184828501610088565b91505092915050565b6100d3816100f4565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea26469706673582212209a159a4f3847890f10bfb87871a61eba91c5dbf5ee3cf6398207e292eee22a1664736f6c63430008070033",
+ "output" =>
+ "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d9565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b60008135905061009781610103565b92915050565b6000602082840312156100b3576100b26100fe565b5b60006100c184828501610088565b91505092915050565b6100d3816100f4565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea26469706673582212209a159a4f3847890f10bfb87871a61eba91c5dbf5ee3cf6398207e292eee22a1664736f6c63430008070033"
+ }
+ }
+ ]}
+ end)
+
+ lowercase_result = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments)
+
+ assert uppercase_result == lowercase_result
+ end
end
describe "fetch_block_internal_transactions/1" do
- test "is not supported", %{json_rpc_named_arguments: json_rpc_named_arguments} do
- EthereumJSONRPC.Geth.fetch_block_internal_transactions([], json_rpc_named_arguments)
+ setup do
+ EthereumJSONRPC.Case.Geth.Mox.setup()
+ end
+
+ test "is supported", %{json_rpc_named_arguments: json_rpc_named_arguments} do
+ block_number = 3_287_375
+ block_quantity = EthereumJSONRPC.integer_to_quantity(block_number)
+ transaction_hash = "0x32b17f27ddb546eab3c4c33f31eb22c1cb992d4ccc50dae26922805b717efe5c"
+
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id, params: [^block_quantity, %{"tracer" => "callTracer"}]}],
+ _ ->
+ {:ok,
+ [
+ %{
+ id: id,
+ result: [
+ %{
+ "result" => %{
+ "calls" => [
+ %{
+ "from" => "0x4200000000000000000000000000000000000015",
+ "gas" => "0xe9a3c",
+ "gasUsed" => "0x4a28",
+ "input" =>
+ "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240",
+ "to" => "0x6df83a19647a398d48e77a6835f4a28eb7e2f7c0",
+ "type" => "DELEGATECALL",
+ "value" => "0x0"
+ }
+ ],
+ "from" => "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001",
+ "gas" => "0xf4240",
+ "gasUsed" => "0xb6f9",
+ "input" =>
+ "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240",
+ "to" => "0x4200000000000000000000000000000000000015",
+ "type" => "CALL",
+ "value" => "0x0"
+ },
+ "txHash" => transaction_hash
+ }
+ ]
+ }
+ ]}
+ end)
+
+ Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s")
+
+ assert {:ok,
+ [
+ %{
+ block_number: 3_287_375,
+ call_type: "call",
+ from_address_hash: "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001",
+ gas: 1_000_000,
+ gas_used: 46841,
+ index: 0,
+ input:
+ "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240",
+ output: "0x",
+ to_address_hash: "0x4200000000000000000000000000000000000015",
+ trace_address: [],
+ transaction_hash: ^transaction_hash,
+ transaction_index: 0,
+ type: "call",
+ value: 0
+ },
+ %{
+ block_number: 3_287_375,
+ call_type: "delegatecall",
+ from_address_hash: "0x4200000000000000000000000000000000000015",
+ gas: 956_988,
+ gas_used: 18984,
+ index: 1,
+ input:
+ "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240",
+ output: "0x",
+ to_address_hash: "0x6df83a19647a398d48e77a6835f4a28eb7e2f7c0",
+ trace_address: [0],
+ transaction_hash: ^transaction_hash,
+ transaction_index: 0,
+ type: "call",
+ value: 0
+ }
+ ]} = Geth.fetch_block_internal_transactions([block_number], json_rpc_named_arguments)
+ end
+
+ test "result is the same as fetch_internal_transactions/2", %{json_rpc_named_arguments: json_rpc_named_arguments} do
+ block_number = 3_287_375
+ block_quantity = EthereumJSONRPC.integer_to_quantity(block_number)
+ transaction_hash = "0x32b17f27ddb546eab3c4c33f31eb22c1cb992d4ccc50dae26922805b717efe5c"
+
+ expect(EthereumJSONRPC.Mox, :json_rpc, 2, fn
+ [%{id: id, params: [^block_quantity, %{"tracer" => "callTracer"}]}], _ ->
+ {:ok,
+ [
+ %{
+ id: id,
+ result: [
+ %{
+ "result" => %{
+ "calls" => [
+ %{
+ "from" => "0x4200000000000000000000000000000000000015",
+ "gas" => "0xe9a3c",
+ "gasUsed" => "0x4a28",
+ "input" =>
+ "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240",
+ "to" => "0x6df83a19647a398d48e77a6835f4a28eb7e2f7c0",
+ "type" => "DELEGATECALL",
+ "value" => "0x0"
+ }
+ ],
+ "from" => "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001",
+ "gas" => "0xf4240",
+ "gasUsed" => "0xb6f9",
+ "input" =>
+ "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240",
+ "to" => "0x4200000000000000000000000000000000000015",
+ "type" => "CALL",
+ "value" => "0x0"
+ },
+ "txHash" => transaction_hash
+ }
+ ]
+ }
+ ]}
+
+ [%{id: id, params: [^transaction_hash, %{"tracer" => "callTracer"}]}], _ ->
+ {:ok,
+ [
+ %{
+ id: id,
+ result: %{
+ "calls" => [
+ %{
+ "from" => "0x4200000000000000000000000000000000000015",
+ "gas" => "0xe9a3c",
+ "gasUsed" => "0x4a28",
+ "input" =>
+ "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240",
+ "to" => "0x6df83a19647a398d48e77a6835f4a28eb7e2f7c0",
+ "type" => "DELEGATECALL",
+ "value" => "0x0"
+ }
+ ],
+ "from" => "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001",
+ "gas" => "0xf4240",
+ "gasUsed" => "0xb6f9",
+ "input" =>
+ "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240",
+ "to" => "0x4200000000000000000000000000000000000015",
+ "type" => "CALL",
+ "value" => "0x0"
+ }
+ }
+ ]}
+ end)
+
+ Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s")
+
+ assert Geth.fetch_block_internal_transactions([block_number], json_rpc_named_arguments) ==
+ Geth.fetch_internal_transactions(
+ [%{block_number: block_number, transaction_index: 0, hash_data: transaction_hash}],
+ json_rpc_named_arguments
+ )
end
end
diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/http/mox_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/http/mox_test.exs
index 052667657d51..09eee029ba42 100644
--- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/http/mox_test.exs
+++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/http/mox_test.exs
@@ -38,12 +38,12 @@ defmodule EthereumJSONRPC.HTTP.MoxTest do
} do
if json_rpc_named_arguments[:transport_options][:http] == EthereumJSONRPC.HTTP.Mox do
EthereumJSONRPC.HTTP.Mox
- |> expect(:json_rpc, 2, fn _url, json, _options ->
+ |> expect(:json_rpc, 2, fn _url, json, _headers, _options ->
assert IO.iodata_to_binary(json) =~ ":13000"
{:ok, %{body: "413 Request Entity Too Large", status_code: 413}}
end)
- |> expect(:json_rpc, fn _url, json, _options ->
+ |> expect(:json_rpc, fn _url, json, _headers, _options ->
json_binary = IO.iodata_to_binary(json)
refute json_binary =~ ":13000"
@@ -58,7 +58,7 @@ defmodule EthereumJSONRPC.HTTP.MoxTest do
{:ok, %{body: body, status_code: 200}}
end)
- |> expect(:json_rpc, fn _url, json, _options ->
+ |> expect(:json_rpc, fn _url, json, _headers, _options ->
json_binary = IO.iodata_to_binary(json)
refute json_binary =~ ":6499"
@@ -107,10 +107,10 @@ defmodule EthereumJSONRPC.HTTP.MoxTest do
if json_rpc_named_arguments[:transport_options][:http] == EthereumJSONRPC.HTTP.Mox do
EthereumJSONRPC.HTTP.Mox
- |> expect(:json_rpc, fn _url, _json, _options ->
+ |> expect(:json_rpc, fn _url, _json, _headers, _options ->
{:ok, %{body: "504 Gateway Timeout", status_code: 504}}
end)
- |> expect(:json_rpc, fn _url, json, _options ->
+ |> expect(:json_rpc, fn _url, json, _headers, _options ->
json_binary = IO.iodata_to_binary(json)
refute json_binary =~ "0xD2849"
@@ -141,7 +141,7 @@ defmodule EthereumJSONRPC.HTTP.MoxTest do
{:ok, %{body: body, status_code: 200}}
end)
- |> expect(:json_rpc, fn _url, json, _options ->
+ |> expect(:json_rpc, fn _url, json, _headers, _options ->
json_binary = IO.iodata_to_binary(json)
refute json_binary =~ "0xD2844"
@@ -199,10 +199,10 @@ defmodule EthereumJSONRPC.HTTP.MoxTest do
if json_rpc_named_arguments[:transport_options][:http] == EthereumJSONRPC.HTTP.Mox do
EthereumJSONRPC.HTTP.Mox
- |> expect(:json_rpc, fn _url, _json, _options ->
+ |> expect(:json_rpc, fn _url, _json, _headers, _options ->
{:error, :timeout}
end)
- |> expect(:json_rpc, fn _url, json, _options ->
+ |> expect(:json_rpc, fn _url, json, _headers, _options ->
json_binary = IO.iodata_to_binary(json)
refute json_binary =~ "0xD2849"
@@ -233,7 +233,7 @@ defmodule EthereumJSONRPC.HTTP.MoxTest do
{:ok, %{body: body, status_code: 200}}
end)
- |> expect(:json_rpc, fn _url, json, _options ->
+ |> expect(:json_rpc, fn _url, json, _headers, _options ->
json_binary = IO.iodata_to_binary(json)
refute json_binary =~ "0xD2844"
@@ -293,7 +293,7 @@ defmodule EthereumJSONRPC.HTTP.MoxTest do
json = Jason.encode_to_iodata!(payload)
http_options = Keyword.fetch!(transport_options, :http_options)
- assert {:ok, %{body: body, status_code: 413}} = http.json_rpc(url, json, http_options)
+ assert {:ok, %{body: body, status_code: 413}} = http.json_rpc(url, json, [], http_options)
assert body =~ "413 Request Entity Too Large"
end
diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/mox_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/mox_test.exs
index 2ad5b8f52c20..f0c4fbaf2d65 100644
--- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/mox_test.exs
+++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/mox_test.exs
@@ -21,8 +21,15 @@ defmodule EthereumJSONRPC.MoxTest do
describe "fetch_block_number_by_tag/2" do
test "with pending with null result", %{json_rpc_named_arguments: json_rpc_named_arguments} do
- expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
- {:ok, nil}
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn [
+ %{
+ id: id,
+ method: "eth_getBlockByNumber",
+ params: ["pending", false]
+ }
+ ],
+ _options ->
+ {:ok, [%{id: id, result: nil}]}
end)
assert {:error, :not_found} = EthereumJSONRPC.fetch_block_number_by_tag("pending", json_rpc_named_arguments)
diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs
index f554c31ce572..945f3f78bc94 100644
--- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs
+++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs
@@ -23,7 +23,6 @@ defmodule EthereumJSONRPC.ReceiptsTest do
index: index,
first_topic: first_topic,
status: status,
- type: type,
transaction_hash: transaction_hash,
transaction_index: transaction_index
} =
@@ -41,7 +40,6 @@ defmodule EthereumJSONRPC.ReceiptsTest do
first_topic: "0xf6db2bace4ac8277384553ad9603d045220a91fb2448ab6130d7a6f044f9a8cf",
gas_used: 106_025,
status: nil,
- type: nil,
transaction_hash: "0xd3efddbbeb6ad8d8bb3f6b8c8fb6165567e9dd868013146bdbeb60953c82822a",
transaction_index: 17
}
@@ -58,7 +56,6 @@ defmodule EthereumJSONRPC.ReceiptsTest do
index: 0,
first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22",
status: :ok,
- type: "mined",
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
transaction_index: 0
}
@@ -89,8 +86,7 @@ defmodule EthereumJSONRPC.ReceiptsTest do
"data" => data,
"logIndex" => integer_to_quantity(index),
"topics" => [first_topic],
- "transactionHash" => transaction_hash,
- "type" => type
+ "transactionHash" => transaction_hash
}
],
"status" => native_status,
diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/request_coordinator_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/request_coordinator_test.exs
index efda05ab86e8..0eaa6c1f3159 100644
--- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/request_coordinator_test.exs
+++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/request_coordinator_test.exs
@@ -26,28 +26,35 @@ defmodule EthereumJSONRPC.RequestCoordinatorTest do
describe "perform/4" do
test "forwards result whenever a request doesn't timeout", %{timeout_table: timeout_table} do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _, _ -> {:ok, %{}} end)
- assert RollingWindow.count(timeout_table, :throttleable_error_count) == 0
- assert {:ok, %{}} == RequestCoordinator.perform(%{}, EthereumJSONRPC.Mox, [], :timer.minutes(60))
- assert RollingWindow.count(timeout_table, :throttleable_error_count) == 0
+ assert RollingWindow.count(timeout_table, :throttleable_error_count_eth_call) == 0
+
+ assert {:ok, %{}} ==
+ RequestCoordinator.perform(%{method: "eth_call"}, EthereumJSONRPC.Mox, [], :timer.minutes(60))
+
+ assert RollingWindow.count(timeout_table, :throttleable_error_count_eth_call) == 0
end
test "increments counter on certain errors", %{timeout_table: timeout_table} do
- expect(EthereumJSONRPC.Mox, :json_rpc, fn :timeout, _ -> {:error, :timeout} end)
- expect(EthereumJSONRPC.Mox, :json_rpc, fn :bad_gateway, _ -> {:error, {:bad_gateway, "message"}} end)
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn %{method: "timeout"}, _ -> {:error, :timeout} end)
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn %{method: "bad_gateway"}, _ -> {:error, {:bad_gateway, "message"}} end)
+
+ assert {:error, :timeout} ==
+ RequestCoordinator.perform(%{method: "timeout"}, EthereumJSONRPC.Mox, [], :timer.minutes(60))
- assert {:error, :timeout} == RequestCoordinator.perform(:timeout, EthereumJSONRPC.Mox, [], :timer.minutes(60))
- assert RollingWindow.count(timeout_table, :throttleable_error_count) == 1
+ assert RollingWindow.count(timeout_table, :throttleable_error_count_timeout) == 1
+ assert RollingWindow.count(timeout_table, :throttleable_error_count_bad_gateway) == 0
assert {:error, {:bad_gateway, "message"}} ==
- RequestCoordinator.perform(:bad_gateway, EthereumJSONRPC.Mox, [], :timer.minutes(60))
+ RequestCoordinator.perform(%{method: "bad_gateway"}, EthereumJSONRPC.Mox, [], :timer.minutes(60))
- assert RollingWindow.count(timeout_table, :throttleable_error_count) == 2
+ assert RollingWindow.count(timeout_table, :throttleable_error_count_timeout) == 1
+ assert RollingWindow.count(timeout_table, :throttleable_error_count_bad_gateway) == 1
end
test "returns timeout error if sleep time will exceed max timeout", %{timeout_table: timeout_table} do
expect(EthereumJSONRPC.Mox, :json_rpc, 0, fn _, _ -> :ok end)
- RollingWindow.inc(timeout_table, :throttleable_error_count)
- assert {:error, :timeout} == RequestCoordinator.perform(%{}, EthereumJSONRPC.Mox, [], 1)
+ RollingWindow.inc(timeout_table, :throttleable_error_count_eth_call)
+ assert {:error, :timeout} == RequestCoordinator.perform(%{method: "eth_call"}, EthereumJSONRPC.Mox, [], 1)
end
test "increments throttle_table even when not an error", %{throttle_table: throttle_table} do
diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs
index bc77cabd5c6a..0409dc1c6492 100644
--- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs
+++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs
@@ -2,6 +2,7 @@ defmodule EthereumJSONRPCTest do
use EthereumJSONRPC.Case, async: true
import EthereumJSONRPC.Case
+ import EthereumJSONRPC, only: [quantity_to_integer: 1]
import Mox
alias EthereumJSONRPC.{Blocks, FetchedBalances, FetchedBeneficiaries, FetchedCodes, Subscription}
@@ -543,61 +544,92 @@ defmodule EthereumJSONRPCTest do
end
end
- describe "fetch_block_number_by_tag" do
- @tag capture_log: false
- test "with earliest", %{json_rpc_named_arguments: json_rpc_named_arguments} do
- if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
- expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
- {:ok, %{"number" => "0x0"}}
- end)
- end
+ describe "fetch_block_by_tag/2" do
+ @supported_tags ~w(earliest latest pending)
- log_bad_gateway(
- fn -> EthereumJSONRPC.fetch_block_number_by_tag("earliest", json_rpc_named_arguments) end,
- fn result ->
- assert {:ok, 0} = result
+ @tag capture_log: false
+ test "with all supported tags", %{json_rpc_named_arguments: json_rpc_named_arguments} do
+ for tag <- @supported_tags do
+ if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn [
+ %{
+ id: id,
+ method: "eth_getBlockByNumber",
+ params: [^tag, false]
+ }
+ ],
+ _options ->
+ block_response(id, tag == "pending", "0x1")
+ end)
end
- )
- end
- @tag capture_log: false
- test "with latest", %{json_rpc_named_arguments: json_rpc_named_arguments} do
- if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
- expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
- {:ok, %{"number" => "0x1"}}
- end)
+ log_bad_gateway(
+ fn -> EthereumJSONRPC.fetch_block_by_tag(tag, json_rpc_named_arguments) end,
+ fn result ->
+ {:ok, %Blocks{blocks_params: [_ | _], transactions_params: []}} = result
+ end
+ )
end
+ end
- log_bad_gateway(
- fn -> EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments) end,
- fn result ->
- assert {:ok, number} = result
- assert number > 0
- end
- )
+ test "unknown errors are returned", %{json_rpc_named_arguments: json_rpc_named_arguments} do
+ # Can't be faked reliably on real chain
+ moxed_json_rpc_named_arguments = Keyword.put(json_rpc_named_arguments, :transport, EthereumJSONRPC.Mox)
+
+ unknown_error = %{"code" => 500, "message" => "Unknown error"}
+
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
+ {:error, unknown_error}
+ end)
+
+ assert {:error, ^unknown_error} = EthereumJSONRPC.fetch_block_by_tag("latest", moxed_json_rpc_named_arguments)
end
+ end
- @tag capture_log: false
- test "with pending", %{json_rpc_named_arguments: json_rpc_named_arguments} do
- if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
- expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
- {:ok, nil}
- end)
- end
+ describe "fetch_block_number_by_tag" do
+ @supported_tags %{"earliest" => "0x0", "latest" => "0x1", "pending" => nil}
- log_bad_gateway(
- fn -> EthereumJSONRPC.fetch_block_number_by_tag("pending", json_rpc_named_arguments) end,
- fn
- # Parity after https://github.com/paritytech/parity-ethereum/pull/8281 and anything spec-compliant
- {:error, reason} ->
- assert reason == :not_found
-
- # Parity before https://github.com/paritytech/parity-ethereum/pull/8281
- {:ok, number} ->
- assert is_integer(number)
- assert number > 0
+ @tag capture_log: false
+ test "with all supported tags", %{json_rpc_named_arguments: json_rpc_named_arguments} do
+ for {tag, expected_result} <- @supported_tags do
+ if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn [
+ %{
+ id: id,
+ method: "eth_getBlockByNumber",
+ params: [^tag, false]
+ }
+ ],
+ _options ->
+ if tag == "pending" do
+ {:ok, [%{id: id, result: nil}]}
+ else
+ block_response(id, false, expected_result)
+ end
+ end)
end
- )
+
+ log_bad_gateway(
+ fn -> EthereumJSONRPC.fetch_block_number_by_tag(tag, json_rpc_named_arguments) end,
+ if tag == "pending" do
+ fn
+ # Parity after https://github.com/paritytech/parity-ethereum/pull/8281 and anything spec-compliant
+ {:error, reason} ->
+ assert reason == :not_found
+
+ # Parity before https://github.com/paritytech/parity-ethereum/pull/8281
+ {:ok, number} ->
+ assert is_integer(number)
+ assert number > 0
+ end
+ else
+ fn result ->
+ integer_result = expected_result && quantity_to_integer(expected_result)
+ assert {:ok, ^integer_result} = result
+ end
+ end
+ )
+ end
end
test "unknown errors are returned", %{json_rpc_named_arguments: json_rpc_named_arguments} do
@@ -912,12 +944,43 @@ defmodule EthereumJSONRPCTest do
:ok
end
end
+
+ defp block_response(id, pending, block_number) do
+ block_hash = "0x29c850324e357f3c0c836d79860c5af55f7b651e5d7ee253c1af1b14908af49c"
+ transaction_hash = "0xa2e81bb56b55ba3dab2daf76501b50dfaad240cccb905dbf89d65c7a84a4a48e"
+
+ {:ok,
+ [
+ %{
+ id: id,
+ result: %{
+ "difficulty" => "0x0",
+ "gasLimit" => "0x0",
+ "gasUsed" => "0x0",
+ "hash" => if(pending, do: nil, else: block_hash),
+ "extraData" => "0x0",
+ "logsBloom" => "0x0",
+ "miner" => "0x0",
+ "number" => block_number,
+ "parentHash" => "0x0",
+ "receiptsRoot" => "0x0",
+ "size" => "0x0",
+ "sha3Uncles" => "0x0",
+ "stateRoot" => "0x0",
+ "timestamp" => "0x0",
+ "totalDifficulty" => "0x0",
+ "transactions" => [transaction_hash],
+ "transactionsRoot" => "0x0",
+ "uncles" => []
+ }
+ }
+ ]}
+ end
end
defmodule EthereumJSONRPCSyncTest do
use EthereumJSONRPC.Case, async: false
- import EthereumJSONRPC.Case
import Mox
alias EthereumJSONRPC.FetchedBalances
@@ -931,26 +994,72 @@ defmodule EthereumJSONRPCSyncTest do
on_exit(fn -> Application.put_all_env([{:indexer, initial_env}]) end)
end
- test "ignores all request with block_quantity != latest when env ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES is true",
+ test "ignores all request with block_quantity != latest or lower than window when env ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES is true",
%{
json_rpc_named_arguments: json_rpc_named_arguments
} do
hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
expected_fetched_balance = 1
- expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn [
- %{
- id: 0,
- jsonrpc: "2.0",
- method: "eth_getBalance",
- params: [^hash, "latest"]
- }
- ],
- _options ->
- {:ok, [%{id: 0, result: EthereumJSONRPC.integer_to_quantity(expected_fetched_balance)}]}
+ expect(EthereumJSONRPC.Mox, :json_rpc, 2, fn
+ [
+ %{
+ id: 0,
+ jsonrpc: "2.0",
+ method: "eth_getBalance",
+ params: [^hash, "0x4"]
+ },
+ %{
+ id: 1,
+ jsonrpc: "2.0",
+ method: "eth_getBalance",
+ params: [^hash, "latest"]
+ }
+ ],
+ _options ->
+ {:ok,
+ [
+ %{id: 0, result: EthereumJSONRPC.integer_to_quantity(expected_fetched_balance)},
+ %{id: 1, result: EthereumJSONRPC.integer_to_quantity(expected_fetched_balance)}
+ ]}
+
+ [
+ %{
+ id: id,
+ method: "eth_getBlockByNumber"
+ }
+ ],
+ _options ->
+ {:ok,
+ [
+ %{
+ id: id,
+ result: %{
+ "difficulty" => "0x0",
+ "gasLimit" => "0x0",
+ "gasUsed" => "0x0",
+ "hash" => "0x29c850324e357f3c0c836d79860c5af55f7b651e5d7ee253c1af1b14908af49c",
+ "extraData" => "0x0",
+ "logsBloom" => "0x0",
+ "miner" => "0x0",
+ "number" => "0x4",
+ "parentHash" => "0x0",
+ "receiptsRoot" => "0x0",
+ "size" => "0x0",
+ "sha3Uncles" => "0x0",
+ "stateRoot" => "0x0",
+ "timestamp" => "0x0",
+ "totalDifficulty" => "0x0",
+ "transactions" => ["0xa2e81bb56b55ba3dab2daf76501b50dfaad240cccb905dbf89d65c7a84a4a48e"],
+ "transactionsRoot" => "0x0",
+ "uncles" => []
+ }
+ }
+ ]}
end)
Application.put_env(:ethereum_jsonrpc, :disable_archive_balances?, "true")
+ Application.put_env(:ethereum_jsonrpc, :archive_balances_window, 1)
assert EthereumJSONRPC.fetch_balances(
[
@@ -969,6 +1078,11 @@ defmodule EthereumJSONRPCSyncTest do
address_hash: hash,
block_number: nil,
value: expected_fetched_balance
+ },
+ %{
+ address_hash: hash,
+ block_number: 4,
+ value: expected_fetched_balance
}
]
}}
diff --git a/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/geth/mox.ex b/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/geth/mox.ex
index d1f113223d68..41a2593cb946 100644
--- a/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/geth/mox.ex
+++ b/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/geth/mox.ex
@@ -6,7 +6,11 @@ defmodule EthereumJSONRPC.Case.Geth.Mox do
def setup do
%{
block_interval: 500,
- json_rpc_named_arguments: [transport: EthereumJSONRPC.Mox, transport_options: [], variant: EthereumJSONRPC.Geth],
+ json_rpc_named_arguments: [
+ transport: EthereumJSONRPC.Mox,
+ transport_options: [http_options: [timeout: 60000, recv_timeout: 60000]],
+ variant: EthereumJSONRPC.Geth
+ ],
subscribe_named_arguments: [transport: EthereumJSONRPC.Mox, transport_options: []]
}
end
diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs
index 4e077d969cb1..eafec8dec772 100644
--- a/apps/explorer/config/config.exs
+++ b/apps/explorer/config/config.exs
@@ -11,7 +11,8 @@ import Config
# General application configuration
config :explorer,
- ecto_repos: [Explorer.Repo, Explorer.Repo.Account],
+ chain_type: ConfigHelper.chain_type(),
+ ecto_repos: ConfigHelper.repos(),
token_functions_reader_max_retries: 3,
# for not fully indexed blockchains
decode_not_a_contract_calls: ConfigHelper.parse_bool_env_var("DECODE_NOT_A_CONTRACT_CALLS")
@@ -100,7 +101,7 @@ config :explorer, Explorer.Counters.AddressTokenTransfersCounter,
enabled: true,
enable_consolidation: true
-config :explorer, Explorer.Counters.BlockBurnedFeeCounter,
+config :explorer, Explorer.Counters.BlockBurntFeeCounter,
enabled: true,
enable_consolidation: true
@@ -108,7 +109,11 @@ config :explorer, Explorer.Counters.BlockPriorityFeeCounter,
enabled: true,
enable_consolidation: true
-config :explorer, Explorer.TokenTransferTokenIdMigration.Supervisor, enabled: true
+config :explorer, Explorer.TokenInstanceOwnerAddressMigration.Supervisor, enabled: true
+
+config :explorer, Explorer.Migrator.TransactionsDenormalization, enabled: true
+config :explorer, Explorer.Migrator.AddressCurrentTokenBalanceTokenType, enabled: true
+config :explorer, Explorer.Migrator.AddressTokenBalanceTokenType, enabled: true
config :explorer, Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand, enabled: true
diff --git a/apps/explorer/config/dev.exs b/apps/explorer/config/dev.exs
index 821c11c17a6d..0aa303a2bfab 100644
--- a/apps/explorer/config/dev.exs
+++ b/apps/explorer/config/dev.exs
@@ -11,6 +11,16 @@ config :explorer, Explorer.Repo.Replica1, timeout: :timer.seconds(80)
# Configure Account database
config :explorer, Explorer.Repo.Account, timeout: :timer.seconds(80)
+# Configure Polygon Edge database
+config :explorer, Explorer.Repo.PolygonEdge, timeout: :timer.seconds(80)
+
+# Configure Polygon zkEVM database
+config :explorer, Explorer.Repo.PolygonZkevm, timeout: :timer.seconds(80)
+
+config :explorer, Explorer.Repo.RSK, timeout: :timer.seconds(80)
+
+config :explorer, Explorer.Repo.Suave, timeout: :timer.seconds(80)
+
config :explorer, Explorer.Tracer, env: "dev", disabled?: true
config :logger, :explorer,
diff --git a/apps/explorer/config/dev/arbitrum.exs b/apps/explorer/config/dev/arbitrum.exs
index 1f7cd963e91e..dafcd640fe4c 100644
--- a/apps/explorer/config/dev/arbitrum.exs
+++ b/apps/explorer/config/dev/arbitrum.exs
@@ -14,6 +14,9 @@ config :explorer,
http: EthereumJSONRPC.HTTP.HTTPoison,
url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545",
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
+ method_to_url: [
+ eth_call: ConfigHelper.eth_call_url("http://localhost:8545")
+ ],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
],
variant: EthereumJSONRPC.Arbitrum
diff --git a/apps/explorer/config/dev/besu.exs b/apps/explorer/config/dev/besu.exs
index 64db4d5b8180..22c0382163e3 100644
--- a/apps/explorer/config/dev/besu.exs
+++ b/apps/explorer/config/dev/besu.exs
@@ -16,7 +16,7 @@ config :explorer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
- eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"),
eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
],
diff --git a/apps/explorer/config/dev/erigon.exs b/apps/explorer/config/dev/erigon.exs
index 9253f8ea3081..163f526996f6 100644
--- a/apps/explorer/config/dev/erigon.exs
+++ b/apps/explorer/config/dev/erigon.exs
@@ -16,7 +16,7 @@ config :explorer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
- eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"),
eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
],
diff --git a/apps/explorer/config/dev/ganache.exs b/apps/explorer/config/dev/ganache.exs
index 8f399f532ff5..f7ddd7cbe37f 100644
--- a/apps/explorer/config/dev/ganache.exs
+++ b/apps/explorer/config/dev/ganache.exs
@@ -14,6 +14,9 @@ config :explorer,
http: EthereumJSONRPC.HTTP.HTTPoison,
url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:7545",
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
+ method_to_url: [
+ eth_call: ConfigHelper.eth_call_url("http://localhost:7545")
+ ],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
],
variant: EthereumJSONRPC.Ganache
diff --git a/apps/explorer/config/dev/geth.exs b/apps/explorer/config/dev/geth.exs
index 4ac8203aecac..8b644ff987d2 100644
--- a/apps/explorer/config/dev/geth.exs
+++ b/apps/explorer/config/dev/geth.exs
@@ -16,6 +16,7 @@ config :explorer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
+ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"),
debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
diff --git a/apps/explorer/config/dev/nethermind.exs b/apps/explorer/config/dev/nethermind.exs
index e92b980507f0..2553a16db492 100644
--- a/apps/explorer/config/dev/nethermind.exs
+++ b/apps/explorer/config/dev/nethermind.exs
@@ -16,7 +16,7 @@ config :explorer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
- eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"),
eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
],
diff --git a/apps/explorer/config/dev/rsk.exs b/apps/explorer/config/dev/rsk.exs
index 597d78c395a0..699584ea0324 100644
--- a/apps/explorer/config/dev/rsk.exs
+++ b/apps/explorer/config/dev/rsk.exs
@@ -16,7 +16,7 @@ config :explorer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
- eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"),
eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
],
diff --git a/apps/explorer/config/prod.exs b/apps/explorer/config/prod.exs
index 43be5c0e9146..e14afe322c04 100644
--- a/apps/explorer/config/prod.exs
+++ b/apps/explorer/config/prod.exs
@@ -16,6 +16,22 @@ config :explorer, Explorer.Repo.Account,
prepare: :unnamed,
timeout: :timer.seconds(60)
+config :explorer, Explorer.Repo.PolygonEdge,
+ prepare: :unnamed,
+ timeout: :timer.seconds(60)
+
+config :explorer, Explorer.Repo.PolygonZkevm,
+ prepare: :unnamed,
+ timeout: :timer.seconds(60)
+
+config :explorer, Explorer.Repo.RSK,
+ prepare: :unnamed,
+ timeout: :timer.seconds(60)
+
+config :explorer, Explorer.Repo.Suave,
+ prepare: :unnamed,
+ timeout: :timer.seconds(60)
+
config :explorer, Explorer.Tracer, env: "production", disabled?: true
config :logger, :explorer,
diff --git a/apps/explorer/config/prod/arbitrum.exs b/apps/explorer/config/prod/arbitrum.exs
index ea4af81646f2..5f45a1a071db 100644
--- a/apps/explorer/config/prod/arbitrum.exs
+++ b/apps/explorer/config/prod/arbitrum.exs
@@ -14,6 +14,9 @@ config :explorer,
http: EthereumJSONRPC.HTTP.HTTPoison,
url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"),
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
+ method_to_url: [
+ eth_call: ConfigHelper.eth_call_url()
+ ],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
],
variant: EthereumJSONRPC.Arbitrum
diff --git a/apps/explorer/config/prod/besu.exs b/apps/explorer/config/prod/besu.exs
index 26df9c5bd480..a486b5c6dcda 100644
--- a/apps/explorer/config/prod/besu.exs
+++ b/apps/explorer/config/prod/besu.exs
@@ -16,7 +16,7 @@ config :explorer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
- eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ eth_call: ConfigHelper.eth_call_url(),
eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL")
],
diff --git a/apps/explorer/config/prod/erigon.exs b/apps/explorer/config/prod/erigon.exs
index 0a954a88b1df..1ca954c1ef41 100644
--- a/apps/explorer/config/prod/erigon.exs
+++ b/apps/explorer/config/prod/erigon.exs
@@ -16,7 +16,7 @@ config :explorer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
- eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ eth_call: ConfigHelper.eth_call_url(),
eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL")
],
diff --git a/apps/explorer/config/prod/ganache.exs b/apps/explorer/config/prod/ganache.exs
index 25de1e5b9a31..0956e4133c06 100644
--- a/apps/explorer/config/prod/ganache.exs
+++ b/apps/explorer/config/prod/ganache.exs
@@ -14,6 +14,9 @@ config :explorer,
http: EthereumJSONRPC.HTTP.HTTPoison,
url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"),
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
+ method_to_url: [
+ eth_call: ConfigHelper.eth_call_url()
+ ],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
],
variant: EthereumJSONRPC.Ganache
diff --git a/apps/explorer/config/prod/geth.exs b/apps/explorer/config/prod/geth.exs
index 3a81acee9c00..46d1f6bc110b 100644
--- a/apps/explorer/config/prod/geth.exs
+++ b/apps/explorer/config/prod/geth.exs
@@ -16,6 +16,7 @@ config :explorer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
+ eth_call: ConfigHelper.eth_call_url(),
debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL")
],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
diff --git a/apps/explorer/config/prod/nethermind.exs b/apps/explorer/config/prod/nethermind.exs
index 4057a0d99d03..bf5a0440865e 100644
--- a/apps/explorer/config/prod/nethermind.exs
+++ b/apps/explorer/config/prod/nethermind.exs
@@ -16,7 +16,7 @@ config :explorer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
- eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ eth_call: ConfigHelper.eth_call_url(),
eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL")
],
diff --git a/apps/explorer/config/prod/rsk.exs b/apps/explorer/config/prod/rsk.exs
index 9f65d8be864f..35120eec2c71 100644
--- a/apps/explorer/config/prod/rsk.exs
+++ b/apps/explorer/config/prod/rsk.exs
@@ -16,7 +16,7 @@ config :explorer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
- eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ eth_call: ConfigHelper.eth_call_url(),
eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL")
],
diff --git a/apps/explorer/config/runtime/test.exs b/apps/explorer/config/runtime/test.exs
index 0afa030023b1..ec346c1d3f89 100644
--- a/apps/explorer/config/runtime/test.exs
+++ b/apps/explorer/config/runtime/test.exs
@@ -24,11 +24,20 @@ config :explorer, Explorer.Chain.Cache.VerifiedContractsCounter, enabled: false,
config :explorer, Explorer.Chain.Cache.NewVerifiedContractsCounter, enabled: false, enable_consolidation: false
config :explorer, Explorer.Chain.Cache.WithdrawalsSum, enabled: false, enable_consolidation: false
+config :explorer, Explorer.Chain.Cache.RootstockLockedBTC,
+ enabled: true,
+ global_ttl: :timer.minutes(10),
+ locking_cap: 21_000_000
+
config :explorer, Explorer.Market.History.Cataloger, enabled: false
config :explorer, Explorer.Tracer, disabled?: false
-config :explorer, Explorer.TokenTransferTokenIdMigration.Supervisor, enabled: false
+config :explorer, Explorer.TokenInstanceOwnerAddressMigration.Supervisor, enabled: false
+
+config :explorer, Explorer.Migrator.TransactionsDenormalization, enabled: false
+config :explorer, Explorer.Migrator.AddressCurrentTokenBalanceTokenType, enabled: false
+config :explorer, Explorer.Migrator.AddressTokenBalanceTokenType, enabled: false
config :explorer,
realtime_events_sender: Explorer.Chain.Events.SimpleSender
diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs
index a5eac2e7352c..b292598e17ff 100644
--- a/apps/explorer/config/test.exs
+++ b/apps/explorer/config/test.exs
@@ -12,7 +12,8 @@ config :explorer, Explorer.Repo,
ownership_timeout: :timer.minutes(7),
timeout: :timer.seconds(60),
queue_target: 1000,
- migration_lock: nil
+ migration_lock: nil,
+ log: false
# Configure API database
config :explorer, Explorer.Repo.Replica1,
@@ -23,9 +24,12 @@ config :explorer, Explorer.Repo.Replica1,
ownership_timeout: :timer.minutes(1),
timeout: :timer.seconds(60),
queue_target: 1000,
- enable_caching_implementation_data_of_proxy: true,
- avg_block_time_as_ttl_cached_implementation_data_of_proxy: false,
- fallback_ttl_cached_implementation_data_of_proxy: :timer.seconds(20),
+ log: false
+
+config :explorer, :proxy,
+ caching_implementation_data_enabled: true,
+ implementation_data_ttl_via_avg_block_time: false,
+ fallback_cached_implementation_data_ttl: :timer.seconds(20),
implementation_data_fetching_timeout: :timer.seconds(20)
# Configure API database
@@ -36,7 +40,20 @@ config :explorer, Explorer.Repo.Account,
# Default of `5_000` was too low for `BlockFetcher` test
ownership_timeout: :timer.minutes(1),
timeout: :timer.seconds(60),
- queue_target: 1000
+ queue_target: 1000,
+ log: false
+
+for repo <- [Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Suave] do
+ config :explorer, repo,
+ database: "explorer_test",
+ hostname: "localhost",
+ pool: Ecto.Adapters.SQL.Sandbox,
+ # Default of `5_000` was too low for `BlockFetcher` test
+ ownership_timeout: :timer.minutes(1),
+ timeout: :timer.seconds(60),
+ queue_target: 1000,
+ log: false
+end
config :logger, :explorer,
level: :warn,
diff --git a/apps/explorer/lib/explorer/account/notifier/email.ex b/apps/explorer/lib/explorer/account/notifier/email.ex
index 35555e316914..713e5c3f1beb 100644
--- a/apps/explorer/lib/explorer/account/notifier/email.ex
+++ b/apps/explorer/lib/explorer/account/notifier/email.ex
@@ -122,27 +122,41 @@ defmodule Explorer.Account.Notifier.Email do
end
defp block_url(notification) do
- URI.to_string(uri()) <> "block/" <> Integer.to_string(notification.block_number)
+ Helpers.block_url(uri(), :show, Integer.to_string(notification.block_number))
end
defp transaction_url(notification) do
Helpers.transaction_url(uri(), :show, notification.transaction_hash)
end
+ defp url_params do
+ Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url]
+ end
+
defp uri do
- %URI{scheme: "https", host: host(), path: path()}
+ %URI{scheme: scheme(), host: host(), port: port(), path: path()}
+ end
+
+ defp scheme do
+ Keyword.get(url_params(), :scheme, "http")
end
defp host do
- if System.get_env("MIX_ENV") == "prod" do
- "blockscout.com"
- else
- Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:host]
- end
+ url_params()[:host]
+ end
+
+ defp port do
+ url_params()[:http][:port]
end
defp path do
- Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:path]
+ raw_path = url_params()[:path]
+
+ if raw_path |> String.ends_with?("/") do
+ raw_path |> String.slice(0..-2)
+ else
+ raw_path
+ end
end
defp sender do
diff --git a/apps/explorer/lib/explorer/account/notifier/forbidden_address.ex b/apps/explorer/lib/explorer/account/notifier/forbidden_address.ex
index 26d5e3836cbe..b20cd898ed2a 100644
--- a/apps/explorer/lib/explorer/account/notifier/forbidden_address.ex
+++ b/apps/explorer/lib/explorer/account/notifier/forbidden_address.ex
@@ -3,16 +3,18 @@ defmodule Explorer.Account.Notifier.ForbiddenAddress do
Check if address is forbidden to notify
"""
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
+
+ alias Explorer.Chain.Address
+
@blacklist [
- "0x0000000000000000000000000000000000000000",
+ burn_address_hash_string(),
"0x000000000000000000000000000000000000dEaD"
]
- alias Explorer.{AccessHelper, Repo}
- alias Explorer.Chain.Token
+ alias Explorer.AccessHelper
- import Ecto.Query, only: [from: 2]
- import Explorer.Chain, only: [string_to_address_hash: 1]
+ import Explorer.Chain, only: [string_to_address_hash: 1, hash_to_address: 1]
def check(address_string) when is_bitstring(address_string) do
case format_address(address_string) do
@@ -30,7 +32,7 @@ defmodule Explorer.Account.Notifier.ForbiddenAddress do
{:error, "This address is blacklisted"}
is_contract(address_hash) ->
- {:error, "This address isn't personal"}
+ {:error, "This address isn't EOA"}
match?({:restricted_access, true}, AccessHelper.restricted_access?(to_string(address_hash), %{})) ->
{:error, "This address has restricted access"}
@@ -41,14 +43,10 @@ defmodule Explorer.Account.Notifier.ForbiddenAddress do
end
defp is_contract(%Explorer.Chain.Hash{} = address_hash) do
- query =
- from(
- token in Token,
- where: token.contract_address_hash == ^address_hash
- )
-
- contract_addresses = Repo.all(query)
- List.first(contract_addresses)
+ case hash_to_address(address_hash) do
+ {:error, :not_found} -> false
+ {:ok, address} -> Address.is_smart_contract(address)
+ end
end
defp format_address(address_hash_string) do
diff --git a/apps/explorer/lib/explorer/account/notifier/notify.ex b/apps/explorer/lib/explorer/account/notifier/notify.ex
index b64b4bd47a5a..f9c9232546ac 100644
--- a/apps/explorer/lib/explorer/account/notifier/notify.ex
+++ b/apps/explorer/lib/explorer/account/notifier/notify.ex
@@ -55,7 +55,8 @@ defmodule Explorer.Account.Notifier.Notify do
defp notify_watchlists(nil), do: nil
defp notify_watchlist(%WatchlistAddress{} = address, summary, direction) do
- case ForbiddenAddress.check(address.address_hash) do
+ case !WatchlistNotification.limit_reached_for_watchlist_id?(address.watchlist_id) &&
+ ForbiddenAddress.check(address.address_hash) do
{:ok, _address_hash} ->
with %WatchlistNotification{} = notification <-
build_watchlist_notification(
@@ -74,6 +75,9 @@ defmodule Explorer.Account.Notifier.Notify do
{:error, _message} ->
nil
+
+ false ->
+ nil
end
end
@@ -96,14 +100,16 @@ defmodule Explorer.Account.Notifier.Notify do
email = Email.compose(notification, address)
- case Mailer.deliver_now(email, response: true) do
- {:ok, _email, response} ->
- Logger.info("--- email delivery response: SUCCESS", fetcher: :account)
- Logger.info(response, fetcher: :account)
+ if email do
+ case Mailer.deliver_now(email, response: true) do
+ {:ok, _email, response} ->
+ Logger.info("--- email delivery response: SUCCESS", fetcher: :account)
+ Logger.info(response, fetcher: :account)
- {:error, error} ->
- Logger.info("--- email delivery response: FAILED", fetcher: :account)
- Logger.info(error, fetcher: :account)
+ {:error, error} ->
+ Logger.info("--- email delivery response: FAILED", fetcher: :account)
+ Logger.info(error, fetcher: :account)
+ end
end
end
@@ -114,6 +120,7 @@ defmodule Explorer.Account.Notifier.Notify do
if is_watched(address, summary, direction) do
%WatchlistNotification{
watchlist_address_id: address.id,
+ watchlist_id: address.watchlist_id,
transaction_hash: summary.transaction_hash,
from_address_hash: summary.from_address_hash,
to_address_hash: summary.to_address_hash,
diff --git a/apps/explorer/lib/explorer/account/notifier/summary.ex b/apps/explorer/lib/explorer/account/notifier/summary.ex
index 2c795ce252d3..db39423bb48c 100644
--- a/apps/explorer/lib/explorer/account/notifier/summary.ex
+++ b/apps/explorer/lib/explorer/account/notifier/summary.ex
@@ -3,7 +3,7 @@ defmodule Explorer.Account.Notifier.Summary do
Compose a summary from transactions
"""
- require Logger
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
alias Explorer
alias Explorer.Account.Notifier.Summary
@@ -156,10 +156,8 @@ defmodule Explorer.Account.Notifier.Summary do
def fetch_summary(_, _), do: :nothing
- @burn_address "0x0000000000000000000000000000000000000000"
-
def method(%{from_address_hash: from, to_address_hash: to}) do
- {:ok, burn_address} = format_address(@burn_address)
+ {:ok, burn_address} = format_address(burn_address_hash_string())
cond do
burn_address == from -> "mint"
diff --git a/apps/explorer/lib/explorer/account/tag_address.ex b/apps/explorer/lib/explorer/account/tag_address.ex
index 546632773ea4..5d38db529a6d 100644
--- a/apps/explorer/lib/explorer/account/tag_address.ex
+++ b/apps/explorer/lib/explorer/account/tag_address.ex
@@ -9,13 +9,11 @@ defmodule Explorer.Account.TagAddress do
alias Ecto.Changeset
alias Explorer.Account.Identity
- alias Explorer.{Chain, Repo}
+ alias Explorer.{Chain, PagingOptions, Repo}
alias Explorer.Chain.{Address, Hash}
import Explorer.Chain, only: [hash_to_lower_case_string: 1]
- @max_tag_address_per_account 15
-
schema "account_tag_addresses" do
field(:address_hash_hash, Cloak.Ecto.SHA256)
field(:name, Explorer.Encrypted.Binary)
@@ -70,12 +68,14 @@ defmodule Explorer.Account.TagAddress do
end
def tag_address_count_constraint(%Changeset{changes: %{identity_id: identity_id}} = tag_address) do
+ max_tags_count = get_max_tags_count()
+
if identity_id
|> tags_address_by_identity_id_query()
- |> limit(@max_tag_address_per_account)
- |> Repo.account_repo().aggregate(:count, :id) >= @max_tag_address_per_account do
+ |> limit(^max_tags_count)
+ |> Repo.account_repo().aggregate(:count, :id) >= max_tags_count do
tag_address
- |> add_error(:name, "Max #{@max_tag_address_per_account} tags per account")
+ |> add_error(:name, "Max #{max_tags_count} tags per account")
else
tag_address
end
@@ -86,18 +86,35 @@ defmodule Explorer.Account.TagAddress do
def tags_address_by_identity_id_query(id) when not is_nil(id) do
__MODULE__
|> where([tag], tag.identity_id == ^id)
- |> order_by([tag], desc: tag.id)
end
def tags_address_by_identity_id_query(_), do: nil
- def get_tags_address_by_identity_id(id) when not is_nil(id) do
+ @doc """
+ Query paginated private address tags by identity id
+ """
+ @spec get_tags_address_by_identity_id(integer(), [Chain.paging_options()]) :: [__MODULE__]
+ def get_tags_address_by_identity_id(id, options \\ [])
+
+ def get_tags_address_by_identity_id(id, options) when not is_nil(id) do
+ paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options())
+
id
|> tags_address_by_identity_id_query()
+ |> order_by([tag], desc: tag.id)
+ |> page_address_tags(paging_options)
+ |> limit(^paging_options.page_size)
|> Repo.account_repo().all()
end
- def get_tags_address_by_identity_id(_), do: nil
+ def get_tags_address_by_identity_id(_, _), do: []
+
+ defp page_address_tags(query, %PagingOptions{key: {id}}) do
+ query
+ |> where([tag], tag.id < ^id)
+ end
+
+ defp page_address_tags(query, _), do: query
def tag_address_by_address_hash_and_identity_id_query(address_hash, identity_id)
when not is_nil(address_hash) and not is_nil(identity_id) do
@@ -152,5 +169,5 @@ defmodule Explorer.Account.TagAddress do
end
end
- def get_max_tags_count, do: @max_tag_address_per_account
+ def get_max_tags_count, do: Application.get_env(:explorer, Explorer.Account)[:private_tags_limit]
end
diff --git a/apps/explorer/lib/explorer/account/tag_transaction.ex b/apps/explorer/lib/explorer/account/tag_transaction.ex
index 086488ab2341..b821fffcf9a0 100644
--- a/apps/explorer/lib/explorer/account/tag_transaction.ex
+++ b/apps/explorer/lib/explorer/account/tag_transaction.ex
@@ -9,11 +9,9 @@ defmodule Explorer.Account.TagTransaction do
alias Ecto.Changeset
alias Explorer.Account.Identity
- alias Explorer.{Chain, Repo}
+ alias Explorer.{Chain, PagingOptions, Repo}
import Explorer.Chain, only: [hash_to_lower_case_string: 1]
- @max_tag_transaction_per_account 15
-
schema "account_tag_transactions" do
field(:tx_hash_hash, Cloak.Ecto.SHA256)
field(:name, Explorer.Encrypted.Binary)
@@ -69,12 +67,14 @@ defmodule Explorer.Account.TagTransaction do
end
def tag_transaction_count_constraint(%Changeset{changes: %{identity_id: identity_id}} = tag_transaction) do
+ max_tags_count = get_max_tags_count()
+
if identity_id
|> tags_transaction_by_identity_id_query()
- |> limit(@max_tag_transaction_per_account)
- |> Repo.account_repo().aggregate(:count, :id) >= @max_tag_transaction_per_account do
+ |> limit(^max_tags_count)
+ |> Repo.account_repo().aggregate(:count, :id) >= max_tags_count do
tag_transaction
- |> add_error(:name, "Max #{@max_tag_transaction_per_account} tags per account")
+ |> add_error(:name, "Max #{max_tags_count} tags per account")
else
tag_transaction
end
@@ -85,18 +85,35 @@ defmodule Explorer.Account.TagTransaction do
def tags_transaction_by_identity_id_query(id) when not is_nil(id) do
__MODULE__
|> where([tag], tag.identity_id == ^id)
- |> order_by([tag], desc: tag.id)
end
def tags_transaction_by_identity_id_query(_), do: nil
- def get_tags_transaction_by_identity_id(id) when not is_nil(id) do
+ @doc """
+ Query paginated private transaction tags by identity id
+ """
+ @spec get_tags_transaction_by_identity_id(integer(), [Chain.paging_options()]) :: [__MODULE__]
+ def get_tags_transaction_by_identity_id(id, options \\ [])
+
+ def get_tags_transaction_by_identity_id(id, options) when not is_nil(id) do
+ paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options())
+
id
|> tags_transaction_by_identity_id_query()
+ |> order_by([tag], desc: tag.id)
+ |> page_transaction_tags(paging_options)
+ |> limit(^paging_options.page_size)
|> Repo.account_repo().all()
end
- def get_tags_transaction_by_identity_id(_), do: nil
+ def get_tags_transaction_by_identity_id(_, _), do: []
+
+ defp page_transaction_tags(query, %PagingOptions{key: {id}}) do
+ query
+ |> where([tag], tag.id < ^id)
+ end
+
+ defp page_transaction_tags(query, _), do: query
def tag_transaction_by_transaction_hash_and_identity_id_query(tx_hash, identity_id)
when not is_nil(tx_hash) and not is_nil(identity_id) do
@@ -151,7 +168,7 @@ defmodule Explorer.Account.TagTransaction do
end
end
- def get_max_tags_count, do: @max_tag_transaction_per_account
+ def get_max_tags_count, do: Application.get_env(:explorer, Explorer.Account)[:private_tags_limit]
end
defimpl Jason.Encoder, for: Explorer.Account.TagTransaction do
diff --git a/apps/explorer/lib/explorer/account/watchlist_address.ex b/apps/explorer/lib/explorer/account/watchlist_address.ex
index 17a10cf63bf8..e488ac758e13 100644
--- a/apps/explorer/lib/explorer/account/watchlist_address.ex
+++ b/apps/explorer/lib/explorer/account/watchlist_address.ex
@@ -10,13 +10,11 @@ defmodule Explorer.Account.WatchlistAddress do
alias Ecto.Changeset
alias Explorer.Account.Notifier.ForbiddenAddress
alias Explorer.Account.Watchlist
- alias Explorer.{Chain, Repo}
+ alias Explorer.{Chain, PagingOptions, Repo}
alias Explorer.Chain.{Address, Wei}
import Explorer.Chain, only: [hash_to_lower_case_string: 1]
- @max_watchlist_addresses_per_account 10
-
schema "account_watchlist_addresses" do
field(:address_hash_hash, Cloak.Ecto.SHA256)
field(:name, Explorer.Encrypted.Binary)
@@ -38,6 +36,9 @@ defmodule Explorer.Account.WatchlistAddress do
field(:notify_inapp, :boolean)
field(:fetched_coin_balance, Wei, virtual: true)
+ field(:tokens_fiat_value, :decimal, virtual: true)
+ field(:tokens_count, :integer, virtual: true)
+ field(:tokens_overflow, :boolean, virtual: true)
timestamps()
end
@@ -76,12 +77,14 @@ defmodule Explorer.Account.WatchlistAddress do
end
def watchlist_address_count_constraint(%Changeset{changes: %{watchlist_id: watchlist_id}} = watchlist_address) do
+ max_watchlist_addresses_count = get_max_watchlist_addresses_count()
+
if watchlist_id
|> watchlist_addresses_by_watchlist_id_query()
- |> limit(@max_watchlist_addresses_per_account)
- |> Repo.account_repo().aggregate(:count, :id) >= @max_watchlist_addresses_per_account do
+ |> limit(^max_watchlist_addresses_count)
+ |> Repo.account_repo().aggregate(:count, :id) >= max_watchlist_addresses_count do
watchlist_address
- |> add_error(:name, "Max #{@max_watchlist_addresses_per_account} watch list addresses per account")
+ |> add_error(:name, "Max #{max_watchlist_addresses_count} watch list addresses per account")
else
watchlist_address
end
@@ -119,6 +122,32 @@ defmodule Explorer.Account.WatchlistAddress do
def watchlist_addresses_by_watchlist_id_query(_), do: nil
+ @doc """
+ Query paginated watchlist addresses by watchlist id
+ """
+ @spec get_watchlist_addresses_by_watchlist_id(integer(), [Chain.paging_options()]) :: [__MODULE__]
+ def get_watchlist_addresses_by_watchlist_id(watchlist_id, options \\ [])
+
+ def get_watchlist_addresses_by_watchlist_id(watchlist_id, options) when not is_nil(watchlist_id) do
+ paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options())
+
+ watchlist_id
+ |> watchlist_addresses_by_watchlist_id_query()
+ |> order_by([wla], desc: wla.id)
+ |> page_watchlist_address(paging_options)
+ |> limit(^paging_options.page_size)
+ |> Repo.account_repo().all()
+ end
+
+ def get_watchlist_addresses_by_watchlist_id(_, _), do: []
+
+ defp page_watchlist_address(query, %PagingOptions{key: {id}}) do
+ query
+ |> where([wla], wla.id < ^id)
+ end
+
+ defp page_watchlist_address(query, _), do: query
+
def watchlist_address_by_id_and_watchlist_id_query(watchlist_address_id, watchlist_id)
when not is_nil(watchlist_address_id) and not is_nil(watchlist_id) do
__MODULE__
@@ -157,7 +186,8 @@ defmodule Explorer.Account.WatchlistAddress do
end
end
- def get_max_watchlist_addresses_count, do: @max_watchlist_addresses_per_account
+ def get_max_watchlist_addresses_count,
+ do: Application.get_env(:explorer, Explorer.Account)[:watchlist_addresses_limit]
def preload_address_fetched_coin_balance(%Watchlist{watchlist_addresses: watchlist_addresses} = watchlist) do
w_addresses =
diff --git a/apps/explorer/lib/explorer/account/watchlist_notification.ex b/apps/explorer/lib/explorer/account/watchlist_notification.ex
index 935e53218780..cc45561073ef 100644
--- a/apps/explorer/lib/explorer/account/watchlist_notification.ex
+++ b/apps/explorer/lib/explorer/account/watchlist_notification.ex
@@ -1,6 +1,6 @@
defmodule Explorer.Account.WatchlistNotification do
@moduledoc """
- Stored notification about event
+ Stored notification about event
related to WatchlistAddress
"""
@@ -9,7 +9,8 @@ defmodule Explorer.Account.WatchlistNotification do
import Ecto.Changeset
import Explorer.Chain, only: [hash_to_lower_case_string: 1]
- alias Explorer.Account.WatchlistAddress
+ alias Explorer.Repo
+ alias Explorer.Account.{Watchlist, WatchlistAddress}
schema "account_watchlist_notifications" do
field(:amount, :decimal)
@@ -24,6 +25,7 @@ defmodule Explorer.Account.WatchlistNotification do
field(:subject_hash, Cloak.Ecto.SHA256)
belongs_to(:watchlist_address, WatchlistAddress)
+ belongs_to(:watchlist, Watchlist)
field(:from_address_hash, Explorer.Encrypted.AddressHash)
field(:to_address_hash, Explorer.Encrypted.AddressHash)
@@ -62,4 +64,23 @@ defmodule Explorer.Account.WatchlistNotification do
|> put_change(:transaction_hash_hash, hash_to_lower_case_string(get_field(changeset, :transaction_hash)))
|> put_change(:subject_hash, get_field(changeset, :subject))
end
+
+ @doc """
+ Check if amount of watchlist notifications for the last 30 days is less than ACCOUNT_WATCHLIST_NOTIFICATIONS_LIMIT_FOR_30_DAYS
+ """
+ @spec limit_reached_for_watchlist_id?(integer) :: boolean
+ def limit_reached_for_watchlist_id?(watchlist_id) do
+ __MODULE__
+ |> where(
+ [wn],
+ wn.watchlist_id == ^watchlist_id and
+ fragment("NOW() - ? at time zone 'UTC' <= interval '30 days'", wn.inserted_at)
+ )
+ |> limit(^watchlist_notification_30_days_limit())
+ |> Repo.account_repo().aggregate(:count) == watchlist_notification_30_days_limit()
+ end
+
+ defp watchlist_notification_30_days_limit do
+ Application.get_env(:explorer, Explorer.Account)[:notifications_limit_for_30_days]
+ end
end
diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex
index 4a00c0ed8d88..52b196489d07 100644
--- a/apps/explorer/lib/explorer/application.ex
+++ b/apps/explorer/lib/explorer/application.ex
@@ -5,12 +5,14 @@ defmodule Explorer.Application do
use Application
- alias Explorer.{Admin, TokenTransferTokenIdMigration}
+ alias Explorer.Admin
alias Explorer.Chain.Cache.{
Accounts,
+ AddressesTabsCounters,
AddressSum,
AddressSumMinusBurnt,
+ BackgroundMigrations,
Block,
BlockNumber,
Blocks,
@@ -18,6 +20,8 @@ defmodule Explorer.Application do
GasUsage,
MinMissingBlockNumber,
NetVersion,
+ PendingBlockOperation,
+ StateChanges,
Transaction,
Transactions,
TransactionsApiV2,
@@ -44,32 +48,38 @@ defmodule Explorer.Application do
base_children = [
Explorer.Repo,
Explorer.Repo.Replica1,
- Explorer.Repo.Account,
Explorer.Vault,
Supervisor.child_spec({SpandexDatadog.ApiServer, datadog_opts()}, id: SpandexDatadog.ApiServer),
Supervisor.child_spec({Task.Supervisor, name: Explorer.HistoryTaskSupervisor}, id: Explorer.HistoryTaskSupervisor),
Supervisor.child_spec({Task.Supervisor, name: Explorer.MarketTaskSupervisor}, id: Explorer.MarketTaskSupervisor),
Supervisor.child_spec({Task.Supervisor, name: Explorer.GenesisDataTaskSupervisor}, id: GenesisDataTaskSupervisor),
Supervisor.child_spec({Task.Supervisor, name: Explorer.TaskSupervisor}, id: Explorer.TaskSupervisor),
+ Supervisor.child_spec({Task.Supervisor, name: Explorer.LookUpSmartContractSourcesTaskSupervisor},
+ id: LookUpSmartContractSourcesTaskSupervisor
+ ),
Explorer.SmartContract.SolcDownloader,
Explorer.SmartContract.VyperDownloader,
{Registry, keys: :duplicate, name: Registry.ChainEvents, id: Registry.ChainEvents},
{Admin.Recovery, [[], [name: Admin.Recovery]]},
- Transaction,
+ Accounts,
AddressSum,
AddressSumMinusBurnt,
+ BackgroundMigrations,
Block,
+ BlockNumber,
Blocks,
GasPriceOracle,
GasUsage,
NetVersion,
- BlockNumber,
- con_cache_child_spec(MarketHistoryCache.cache_name()),
- con_cache_child_spec(RSK.cache_name(), ttl_check_interval: :timer.minutes(1), global_ttl: :timer.minutes(30)),
+ PendingBlockOperation,
+ Transaction,
+ StateChanges,
Transactions,
TransactionsApiV2,
- Accounts,
Uncles,
+ AddressesTabsCounters,
+ con_cache_child_spec(MarketHistoryCache.cache_name()),
+ con_cache_child_spec(RSK.cache_name(), ttl_check_interval: :timer.minutes(1), global_ttl: :timer.minutes(30)),
{Redix, redix_opts()},
{Explorer.Utility.MissingRangesManipulator, []}
]
@@ -82,41 +92,64 @@ defmodule Explorer.Application do
end
defp configurable_children do
- [
- configure(Explorer.ExchangeRates),
- configure(Explorer.ExchangeRates.TokenExchangeRates),
- configure(Explorer.ChainSpec.GenesisData),
- configure(Explorer.Market.History.Cataloger),
- configure(Explorer.Chain.Cache.ContractsCounter),
- configure(Explorer.Chain.Cache.NewContractsCounter),
- configure(Explorer.Chain.Cache.VerifiedContractsCounter),
- configure(Explorer.Chain.Cache.NewVerifiedContractsCounter),
- configure(Explorer.Chain.Cache.TransactionActionTokensData),
- configure(Explorer.Chain.Cache.TransactionActionUniswapPools),
- configure(Explorer.Chain.Cache.WithdrawalsSum),
- configure(Explorer.Chain.Transaction.History.Historian),
- configure(Explorer.Chain.Events.Listener),
- configure(Explorer.Counters.AddressesWithBalanceCounter),
- configure(Explorer.Counters.AddressesCounter),
- configure(Explorer.Counters.AddressTransactionsCounter),
- configure(Explorer.Counters.AddressTokenTransfersCounter),
- configure(Explorer.Counters.AddressTransactionsGasUsageCounter),
- configure(Explorer.Counters.AddressTokenUsdSum),
- configure(Explorer.Counters.TokenHoldersCounter),
- configure(Explorer.Counters.TokenTransfersCounter),
- configure(Explorer.Counters.BlockBurnedFeeCounter),
- configure(Explorer.Counters.BlockPriorityFeeCounter),
- configure(Explorer.Counters.AverageBlockTime),
- configure(Explorer.Counters.Bridge),
- configure(Explorer.Validator.MetadataProcessor),
- configure(Explorer.Tags.AddressTag.Cataloger),
- configure(MinMissingBlockNumber),
- configure(TokenTransferTokenIdMigration.Supervisor),
- configure(Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand),
- configure(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand),
- sc_microservice_configure(Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand)
- ]
- |> List.flatten()
+ configurable_children_set =
+ [
+ configure(Explorer.ExchangeRates),
+ configure(Explorer.ExchangeRates.TokenExchangeRates),
+ configure(Explorer.ChainSpec.GenesisData),
+ configure(Explorer.Market.History.Cataloger),
+ configure(Explorer.Chain.Cache.ContractsCounter),
+ configure(Explorer.Chain.Cache.NewContractsCounter),
+ configure(Explorer.Chain.Cache.VerifiedContractsCounter),
+ configure(Explorer.Chain.Cache.NewVerifiedContractsCounter),
+ configure(Explorer.Chain.Cache.TransactionActionTokensData),
+ configure(Explorer.Chain.Cache.TransactionActionUniswapPools),
+ configure(Explorer.Chain.Cache.WithdrawalsSum),
+ configure(Explorer.Chain.Transaction.History.Historian),
+ configure(Explorer.Chain.Events.Listener),
+ configure(Explorer.Counters.AddressesWithBalanceCounter),
+ configure(Explorer.Counters.AddressesCounter),
+ configure(Explorer.Counters.AddressTransactionsCounter),
+ configure(Explorer.Counters.AddressTokenTransfersCounter),
+ configure(Explorer.Counters.AddressTransactionsGasUsageCounter),
+ configure(Explorer.Counters.AddressTokenUsdSum),
+ configure(Explorer.Counters.TokenHoldersCounter),
+ configure(Explorer.Counters.TokenTransfersCounter),
+ configure(Explorer.Counters.BlockBurntFeeCounter),
+ configure(Explorer.Counters.BlockPriorityFeeCounter),
+ configure(Explorer.Counters.AverageBlockTime),
+ configure(Explorer.Counters.Bridge),
+ configure(Explorer.Validator.MetadataProcessor),
+ configure(Explorer.Tags.AddressTag.Cataloger),
+ configure(MinMissingBlockNumber),
+ configure(Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand),
+ configure(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand),
+ configure(Explorer.TokenInstanceOwnerAddressMigration.Supervisor),
+ sc_microservice_configure(Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand),
+ configure(Explorer.Chain.Cache.RootstockLockedBTC),
+ configure(Explorer.Migrator.TransactionsDenormalization),
+ configure(Explorer.Migrator.AddressCurrentTokenBalanceTokenType),
+ configure(Explorer.Migrator.AddressTokenBalanceTokenType)
+ ]
+ |> List.flatten()
+
+ repos_by_chain_type() ++ account_repo() ++ configurable_children_set
+ end
+
+ defp repos_by_chain_type do
+ if Mix.env() == :test do
+ [Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Suave]
+ else
+ []
+ end
+ end
+
+ defp account_repo do
+ if System.get_env("ACCOUNT_DATABASE_URL") || Mix.env() == :test do
+ [Explorer.Repo.Account]
+ else
+ []
+ end
end
defp should_start?(process) do
@@ -132,9 +165,7 @@ defmodule Explorer.Application do
end
defp sc_microservice_configure(process) do
- config = Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, [])
-
- if config[:enabled] && config[:type] == "eth_bytecode_db" do
+ if Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour)[:eth_bytecode_db?] do
process
else
[]
diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex
index 64417ffdf9eb..e61f9cf6ca22 100644
--- a/apps/explorer/lib/explorer/chain.ex
+++ b/apps/explorer/lib/explorer/chain.ex
@@ -33,6 +33,7 @@ defmodule Explorer.Chain do
alias Ecto.{Changeset, Multi}
alias EthereumJSONRPC.Transaction, as: EthereumJSONRPCTransaction
+ alias EthereumJSONRPC.Utility.RangesHelper
alias Explorer.Account.WatchlistAddress
@@ -50,13 +51,13 @@ defmodule Explorer.Chain do
CurrencyHelper,
Data,
DecompiledSmartContract,
+ DenormalizationHelper,
Hash,
Import,
InternalTransaction,
Log,
PendingBlockOperation,
SmartContract,
- SmartContractAdditionalSource,
Token,
Token.Instance,
TokenTransfer,
@@ -68,7 +69,6 @@ defmodule Explorer.Chain do
alias Explorer.Chain.Block.{EmissionReward, Reward}
alias Explorer.Chain.Cache.{
- Accounts,
BlockNumber,
Blocks,
ContractsCounter,
@@ -81,22 +81,14 @@ defmodule Explorer.Chain do
}
alias Explorer.Chain.Cache.Block, as: BlockCache
- alias Explorer.Chain.Cache.Helper, as: CacheHelper
+ alias Explorer.Chain.Cache.PendingBlockOperation, as: PendingBlockOperationCache
alias Explorer.Chain.Fetcher.{CheckBytecodeMatchingOnDemand, LookUpSmartContractSourcesOnDemand}
alias Explorer.Chain.Import.Runner
alias Explorer.Chain.InternalTransaction.{CallType, Type}
-
- alias Explorer.Counters.{
- AddressesCounter,
- AddressesWithBalanceCounter,
- AddressTokenTransfersCounter,
- AddressTransactionsCounter,
- AddressTransactionsGasUsageCounter
- }
+ alias Explorer.Chain.SmartContract.Proxy.EIP1167
alias Explorer.Market.MarketHistoryCache
alias Explorer.{PagingOptions, Repo}
- alias Explorer.SmartContract.Helper
alias Dataloader.Ecto, as: DataloaderEcto
@@ -108,7 +100,7 @@ defmodule Explorer.Chain do
[to_address: :smart_contract] => :optional,
[from_address: :names] => :optional,
[to_address: :names] => :optional,
- token: :required
+ token: :optional
}
@method_name_to_id_map %{
@@ -119,8 +111,6 @@ defmodule Explorer.Chain do
"commit" => "f14fcbc8"
}
- @max_incoming_transactions_count 10_000
-
@revert_msg_prefix_1 "Revert: "
@revert_msg_prefix_2 "revert: "
@revert_msg_prefix_3 "reverted "
@@ -130,8 +120,6 @@ defmodule Explorer.Chain do
# keccak256("Error(string)")
@revert_error_method_id "08c379a0"
- @burn_address_hash_str "0x0000000000000000000000000000000000000000"
-
@limit_showing_transactions 10_000
@default_page_size 50
@@ -173,59 +161,10 @@ defmodule Explorer.Chain do
"""
@type necessity_by_association :: %{association => necessity}
- @typep necessity_by_association_option :: {:necessity_by_association, necessity_by_association}
- @typep paging_options :: {:paging_options, PagingOptions.t()}
+ @type necessity_by_association_option :: {:necessity_by_association, necessity_by_association}
+ @type paging_options :: {:paging_options, PagingOptions.t()}
@typep balance_by_day :: %{date: String.t(), value: Wei.t()}
- @typep api? :: {:api?, true | false}
-
- @doc """
- Gets from the cache the count of `t:Explorer.Chain.Address.t/0`'s where the `fetched_coin_balance` is > 0
- """
- @spec count_addresses_with_balance_from_cache :: non_neg_integer()
- def count_addresses_with_balance_from_cache do
- AddressesWithBalanceCounter.fetch()
- end
-
- @doc """
- Estimated count of `t:Explorer.Chain.Address.t/0`.
-
- Estimated count of addresses.
- """
- @spec address_estimated_count() :: non_neg_integer()
- def address_estimated_count(options \\ []) do
- cached_value = AddressesCounter.fetch()
-
- if is_nil(cached_value) || cached_value == 0 do
- count = CacheHelper.estimated_count_from("addresses", options)
-
- max(count, 0)
- else
- cached_value
- end
- end
-
- @doc """
- Counts the number of addresses with fetched coin balance > 0.
-
- This function should be used with caution. In larger databases, it may take a
- while to have the return back.
- """
- def count_addresses_with_balance do
- Repo.one(
- Address.count_with_fetched_coin_balance(),
- timeout: :infinity
- )
- end
-
- @doc """
- Counts the number of all addresses.
-
- This function should be used with caution. In larger databases, it may take a
- while to have the return back.
- """
- def count_addresses do
- Repo.aggregate(Address, :count, timeout: :infinity)
- end
+ @type api? :: {:api?, true | false}
@doc """
`t:Explorer.Chain.InternalTransaction/0`s from the address with the given `hash`.
@@ -259,12 +198,12 @@ defmodule Explorer.Chain do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
- if direction == nil do
+ if direction == nil || direction == "" do
query_to_address_hash_wrapped =
InternalTransaction
|> InternalTransaction.where_nonpending_block()
|> InternalTransaction.where_address_fields_match(hash, :to_address_hash)
- |> InternalTransaction.where_block_number_in_period(from_block, to_block)
+ |> where_block_number_in_period(from_block, to_block)
|> common_where_limit_order(paging_options)
|> wrapped_union_subquery()
@@ -272,7 +211,7 @@ defmodule Explorer.Chain do
InternalTransaction
|> InternalTransaction.where_nonpending_block()
|> InternalTransaction.where_address_fields_match(hash, :from_address_hash)
- |> InternalTransaction.where_block_number_in_period(from_block, to_block)
+ |> where_block_number_in_period(from_block, to_block)
|> common_where_limit_order(paging_options)
|> wrapped_union_subquery()
@@ -280,7 +219,7 @@ defmodule Explorer.Chain do
InternalTransaction
|> InternalTransaction.where_nonpending_block()
|> InternalTransaction.where_address_fields_match(hash, :created_contract_address_hash)
- |> InternalTransaction.where_block_number_in_period(from_block, to_block)
+ |> where_block_number_in_period(from_block, to_block)
|> common_where_limit_order(paging_options)
|> wrapped_union_subquery()
@@ -289,16 +228,16 @@ defmodule Explorer.Chain do
|> union(^query_created_contract_address_hash_wrapped)
|> wrapped_union_subquery()
|> common_where_limit_order(paging_options)
- |> preload(transaction: :block)
+ |> preload(:block)
|> join_associations(necessity_by_association)
|> select_repo(options).all()
else
InternalTransaction
|> InternalTransaction.where_nonpending_block()
|> InternalTransaction.where_address_fields_match(hash, direction)
- |> InternalTransaction.where_block_number_in_period(from_block, to_block)
+ |> where_block_number_in_period(from_block, to_block)
|> common_where_limit_order(paging_options)
- |> preload(transaction: :block)
+ |> preload(:block)
|> join_associations(necessity_by_association)
|> select_repo(options).all()
end
@@ -314,7 +253,6 @@ defmodule Explorer.Chain do
defp common_where_limit_order(query, paging_options) do
query
|> InternalTransaction.where_is_different_from_parent_transaction()
- |> InternalTransaction.where_block_number_is_not_null()
|> page_internal_transaction(paging_options, %{index_int_tx_desc_order: true})
|> limit(^paging_options.page_size)
|> order_by(
@@ -325,255 +263,30 @@ defmodule Explorer.Chain do
)
end
- @doc """
- Get the total number of transactions sent by the address with the given hash according to the last block indexed.
-
- We have to increment +1 in the last nonce result because it works like an array position, the first
- nonce has the value 0. When last nonce is nil, it considers that the given address has 0 transactions.
- """
- @spec total_transactions_sent_by_address(Hash.Address.t()) :: non_neg_integer()
- def total_transactions_sent_by_address(address_hash) do
- last_nonce =
- address_hash
- |> Transaction.last_nonce_by_address_query()
- |> Repo.one(timeout: :infinity)
-
- case last_nonce do
- nil -> 0
- value -> value + 1
- end
- end
-
- @doc """
- Fetches the transactions related to the address with the given hash, including
- transactions that only have the address in the `token_transfers` related table
- and rewards for block validation.
-
- This query is divided into multiple subqueries intentionally in order to
- improve the listing performance.
-
- The `token_transfers` table tends to grow exponentially, and the query results
- with a `transactions` `join` statement takes too long.
-
- To solve this the `transaction_hashes` are fetched in a separate query, and
- paginated through the `block_number` already present in the `token_transfers`
- table.
-
- ## Options
-
- * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
- `:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the
- `t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`.
- * `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and
- `:key` (a tuple of the lowest/oldest `{block_number, index}`) and. Results will be the transactions older than
- the `block_number` and `index` that are passed.
-
- """
- @spec address_to_transactions_with_rewards(Hash.Address.t(), [paging_options | necessity_by_association_option]) ::
- [
- Transaction.t()
- ]
- def address_to_transactions_with_rewards(address_hash, options \\ []) when is_list(options) do
- paging_options = Keyword.get(options, :paging_options, @default_paging_options)
-
- if Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:has_emission_funds] do
- cond do
- Keyword.get(options, :direction) == :from ->
- address_to_transactions_without_rewards(address_hash, options)
-
- address_has_rewards?(address_hash) ->
- address_with_rewards(address_hash, options, paging_options)
-
- true ->
- address_to_transactions_without_rewards(address_hash, options)
- end
- else
- address_to_transactions_without_rewards(address_hash, options)
- end
- end
-
- defp address_with_rewards(address_hash, options, paging_options) do
- %{payout_key: block_miner_payout_address} = Reward.get_validator_payout_key_by_mining_from_db(address_hash, options)
-
- if block_miner_payout_address && address_hash == block_miner_payout_address do
- transactions_with_rewards_results(address_hash, options, paging_options)
- else
- address_to_transactions_without_rewards(address_hash, options)
- end
- end
-
- defp transactions_with_rewards_results(address_hash, options, paging_options) do
- blocks_range = address_to_transactions_tasks_range_of_blocks(address_hash, options)
-
- rewards_task =
- Task.async(fn -> Reward.fetch_emission_rewards_tuples(address_hash, paging_options, blocks_range, options) end)
-
- [rewards_task | address_to_transactions_tasks(address_hash, options)]
- |> wait_for_address_transactions()
- |> Enum.sort_by(fn item ->
- case item do
- {%Reward{} = emission_reward, _} ->
- {-emission_reward.block.number, 1}
-
- item ->
- process_item(item)
- end
- end)
- |> Enum.dedup_by(fn item ->
- case item do
- {%Reward{} = emission_reward, _} ->
- {emission_reward.block_hash, emission_reward.address_hash, emission_reward.address_type}
-
- transaction ->
- transaction.hash
- end
- end)
- |> Enum.take(paging_options.page_size)
- end
-
- defp process_item(item) do
- block_number = if item.block_number, do: -item.block_number, else: 0
- index = if item.index, do: -item.index, else: 0
- {block_number, index}
- end
-
- def address_to_transactions_without_rewards(address_hash, options) do
- paging_options = Keyword.get(options, :paging_options, @default_paging_options)
-
- address_hash
- |> address_to_transactions_tasks(options)
- |> wait_for_address_transactions()
- |> Enum.sort_by(&{&1.block_number, &1.index}, &>=/2)
- |> Enum.dedup_by(& &1.hash)
- |> Enum.take(paging_options.page_size)
- end
-
def address_hashes_to_mined_transactions_without_rewards(address_hashes, options) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
address_hashes
|> address_hashes_to_mined_transactions_tasks(options)
- |> wait_for_address_transactions()
+ |> Transaction.wait_for_address_transactions()
|> Enum.sort_by(&{&1.block_number, &1.index}, &>=/2)
|> Enum.dedup_by(& &1.hash)
|> Enum.take(paging_options.page_size)
end
- defp address_to_transactions_tasks_query(options, only_mined? \\ false) do
- from_block = from_block(options)
- to_block = to_block(options)
-
- options
- |> Keyword.get(:paging_options, @default_paging_options)
- |> fetch_transactions(from_block, to_block, !only_mined?)
- end
-
- defp transactions_block_numbers_at_address(address_hash, options) do
- direction = Keyword.get(options, :direction)
-
- options
- |> address_to_transactions_tasks_query()
- |> Transaction.not_pending_transactions()
- |> select([t], t.block_number)
- |> Transaction.matching_address_queries_list(direction, address_hash)
- end
-
- defp address_to_transactions_tasks(address_hash, options) do
- direction = Keyword.get(options, :direction)
- necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
-
- from_block = from_block(options)
- to_block = to_block(options)
-
- options
- |> address_to_transactions_tasks_query()
- |> Transaction.not_dropped_or_replaced_transactions()
- |> where_block_number_in_period(from_block, to_block)
- |> join_associations(necessity_by_association)
- |> Transaction.matching_address_queries_list(direction, address_hash)
- |> Enum.map(fn query -> Task.async(fn -> select_repo(options).all(query) end) end)
- end
-
defp address_hashes_to_mined_transactions_tasks(address_hashes, options) do
direction = Keyword.get(options, :direction)
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
options
- |> address_to_transactions_tasks_query(true)
+ |> Transaction.address_to_transactions_tasks_query(true)
|> Transaction.not_pending_transactions()
|> join_associations(necessity_by_association)
+ |> Transaction.put_has_token_transfers_to_tx(false)
|> Transaction.matching_address_queries_list(direction, address_hashes)
|> Enum.map(fn query -> Task.async(fn -> select_repo(options).all(query) end) end)
end
- def address_to_transactions_tasks_range_of_blocks(address_hash, options) do
- extremums_list =
- address_hash
- |> transactions_block_numbers_at_address(options)
- |> Enum.map(fn query ->
- extremum_query =
- from(
- q in subquery(query),
- select: %{min_block_number: min(q.block_number), max_block_number: max(q.block_number)}
- )
-
- extremum_query
- |> Repo.one!()
- end)
-
- extremums_list
- |> Enum.reduce(%{min_block_number: nil, max_block_number: 0}, fn %{
- min_block_number: min_number,
- max_block_number: max_number
- },
- extremums_result ->
- current_min_number = Map.get(extremums_result, :min_block_number)
- current_max_number = Map.get(extremums_result, :max_block_number)
-
- extremums_result
- |> process_extremums_result_against_min_number(current_min_number, min_number)
- |> process_extremums_result_against_max_number(current_max_number, max_number)
- end)
- end
-
- defp process_extremums_result_against_min_number(extremums_result, current_min_number, min_number)
- when is_number(current_min_number) and
- not (is_number(min_number) and min_number > 0 and min_number < current_min_number) do
- extremums_result
- end
-
- defp process_extremums_result_against_min_number(extremums_result, _current_min_number, min_number) do
- extremums_result
- |> Map.put(:min_block_number, min_number)
- end
-
- defp process_extremums_result_against_max_number(extremums_result, current_max_number, max_number)
- when is_number(max_number) and max_number > 0 and max_number > current_max_number do
- extremums_result
- |> Map.put(:max_block_number, max_number)
- end
-
- defp process_extremums_result_against_max_number(extremums_result, _current_max_number, _max_number) do
- extremums_result
- end
-
- defp wait_for_address_transactions(tasks) do
- tasks
- |> Task.yield_many(:timer.seconds(20))
- |> Enum.flat_map(fn {_task, res} ->
- case res do
- {:ok, result} ->
- result
-
- {:exit, reason} ->
- raise "Query fetching address transactions terminated: #{inspect(reason)}"
-
- nil ->
- raise "Query fetching address transactions timed out."
- end
- end)
- end
-
@spec address_hash_to_token_transfers(Hash.Address.t(), Keyword.t()) :: [Transaction.t()]
def address_hash_to_token_transfers(address_hash, options \\ []) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
@@ -582,7 +295,7 @@ defmodule Explorer.Chain do
direction
|> Transaction.transactions_with_token_transfers_direction(address_hash)
|> Transaction.preload_token_transfers(address_hash)
- |> handle_paging_options(paging_options)
+ |> Transaction.handle_paging_options(paging_options)
|> Repo.all()
end
@@ -632,116 +345,46 @@ defmodule Explorer.Chain do
|> select_repo(options).all()
end
- @doc """
- address_hash_to_token_transfers_including_contract/2 function returns token transfers on address (to/from/contract).
- It is used by CSV export of token transfers button.
- """
- @spec address_hash_to_token_transfers_including_contract(Hash.Address.t(), Keyword.t()) :: [TokenTransfer.t()]
- def address_hash_to_token_transfers_including_contract(address_hash, options \\ []) do
- paging_options = Keyword.get(options, :paging_options, @default_paging_options)
- from_block = Keyword.get(options, :from_block)
- to_block = Keyword.get(options, :to_block)
-
- query =
- from_block
- |> query_address_hash_to_token_transfers_including_contract(to_block, address_hash)
- |> order_by([token_transfer], asc: token_transfer.block_number, asc: token_transfer.log_index)
-
- query
- |> handle_token_transfer_paging_options(paging_options)
- |> preload(transaction: :block)
- |> preload(:token)
- |> Repo.all()
- end
-
- defp query_address_hash_to_token_transfers_including_contract(nil, to_block, address_hash)
- when not is_nil(to_block) do
- from(
- token_transfer in TokenTransfer,
- where:
- (token_transfer.to_address_hash == ^address_hash or
- token_transfer.from_address_hash == ^address_hash or
- token_transfer.token_contract_address_hash == ^address_hash) and
- token_transfer.block_number <= ^to_block
- )
- end
-
- defp query_address_hash_to_token_transfers_including_contract(from_block, nil, address_hash)
- when not is_nil(from_block) do
- from(
- token_transfer in TokenTransfer,
- where:
- (token_transfer.to_address_hash == ^address_hash or
- token_transfer.from_address_hash == ^address_hash or
- token_transfer.token_contract_address_hash == ^address_hash) and
- token_transfer.block_number >= ^from_block
- )
- end
-
- defp query_address_hash_to_token_transfers_including_contract(from_block, to_block, address_hash)
- when not is_nil(from_block) and not is_nil(to_block) do
- from(
- token_transfer in TokenTransfer,
- where:
- (token_transfer.to_address_hash == ^address_hash or
- token_transfer.from_address_hash == ^address_hash or
- token_transfer.token_contract_address_hash == ^address_hash) and
- (token_transfer.block_number >= ^from_block and token_transfer.block_number <= ^to_block)
- )
- end
-
- defp query_address_hash_to_token_transfers_including_contract(_, _, address_hash) do
- from(
- token_transfer in TokenTransfer,
- where:
- token_transfer.to_address_hash == ^address_hash or
- token_transfer.from_address_hash == ^address_hash or
- token_transfer.token_contract_address_hash == ^address_hash
- )
- end
-
@spec address_to_logs(Hash.Address.t(), Keyword.t()) :: [Log.t()]
- def address_to_logs(address_hash, options \\ []) when is_list(options) do
+ def address_to_logs(address_hash, csv_export?, options \\ []) when is_list(options) do
paging_options = Keyword.get(options, :paging_options) || %PagingOptions{page_size: 50}
from_block = from_block(options)
to_block = to_block(options)
- {block_number, transaction_index, log_index} = paging_options.key || {BlockNumber.get_max(), 0, 0}
-
base =
- from(log in Log,
- inner_join: transaction in Transaction,
- on: transaction.hash == log.transaction_hash,
- order_by: [desc: log.block_number, desc: log.index],
- where: transaction.block_number < ^block_number,
- or_where: transaction.block_number == ^block_number and transaction.index > ^transaction_index,
- or_where:
- transaction.block_number == ^block_number and transaction.index == ^transaction_index and
- log.index > ^log_index,
- where: log.address_hash == ^address_hash,
- limit: ^paging_options.page_size,
- select: log
- )
-
- base_query =
- base
- |> filter_topic(Keyword.get(options, :topic))
+ if DenormalizationHelper.denormalization_finished?() do
+ from(log in Log,
+ order_by: [desc: log.block_number, desc: log.index],
+ where: log.address_hash == ^address_hash,
+ limit: ^paging_options.page_size,
+ select: log,
+ inner_join: transaction in assoc(log, :transaction),
+ where: transaction.block_consensus == true
+ )
+ else
+ from(log in Log,
+ order_by: [desc: log.block_number, desc: log.index],
+ where: log.address_hash == ^address_hash,
+ limit: ^paging_options.page_size,
+ select: log,
+ inner_join: block in Block,
+ on: block.hash == log.block_hash,
+ where: block.consensus == true
+ )
+ end
- wrapped_query =
- from(
- log in subquery(base_query),
- inner_join: transaction in Transaction,
- on: transaction.hash == log.transaction_hash,
- preload: [:transaction, transaction: [to_address: :smart_contract]],
- where:
- log.block_hash == transaction.block_hash and
- log.block_number == transaction.block_number and
- log.transaction_hash == transaction.hash,
- select: log
- )
+ preloaded_query =
+ if csv_export? do
+ base
+ else
+ base
+ |> preload(transaction: [:to_address, :from_address])
+ end
- wrapped_query
+ preloaded_query
+ |> page_logs(paging_options)
+ |> filter_topic(Keyword.get(options, :topic))
|> where_block_number_in_period(from_block, to_block)
|> select_repo(options).all()
|> Enum.take(paging_options.page_size)
@@ -749,6 +392,8 @@ defmodule Explorer.Chain do
defp filter_topic(base_query, nil), do: base_query
+ defp filter_topic(base_query, ""), do: base_query
+
defp filter_topic(base_query, topic) do
from(log in base_query,
where:
@@ -796,7 +441,7 @@ defmodule Explorer.Chain do
address_hash
|> Transaction.transactions_with_token_transfers(token_hash)
|> Transaction.preload_token_transfers(address_hash)
- |> handle_paging_options(paging_options)
+ |> Transaction.handle_paging_options(paging_options)
|> Repo.all()
end
@@ -847,7 +492,7 @@ defmodule Explorer.Chain do
def block_reward(block_number) do
block_hash =
Block
- |> where([block], block.number == ^block_number and block.consensus)
+ |> where([block], block.number == ^block_number and block.consensus == true)
|> select([block], block.hash)
|> Repo.one!()
@@ -867,7 +512,7 @@ defmodule Explorer.Chain do
left_join: transaction in assoc(block, :transactions),
inner_join: emission_reward in EmissionReward,
on: fragment("? <@ ?", block.number, emission_reward.block_range),
- where: block.number == ^block_number and block.consensus,
+ where: block.number == ^block_number and block.consensus == true,
group_by: [emission_reward.reward, block.hash],
select: %Wei{
value: coalesce(sum(transaction.gas_used * transaction.gas_price), 0) + emission_reward.reward
@@ -880,65 +525,6 @@ defmodule Explorer.Chain do
end
end
- def txn_fees(transactions) do
- Enum.reduce(transactions, Decimal.new(0), fn %{gas_used: gas_used, gas_price: gas_price}, acc ->
- gas_used
- |> Decimal.new()
- |> Decimal.mult(gas_price_to_decimal(gas_price))
- |> Decimal.add(acc)
- end)
- end
-
- defp gas_price_to_decimal(%Wei{} = wei), do: wei.value
- defp gas_price_to_decimal(gas_price), do: Decimal.new(gas_price)
-
- def burned_fees(transactions, base_fee_per_gas) do
- burned_fee_counter =
- transactions
- |> Enum.reduce(Decimal.new(0), fn %{gas_used: gas_used}, acc ->
- gas_used
- |> Decimal.new()
- |> Decimal.add(acc)
- end)
-
- base_fee_per_gas && Wei.mult(base_fee_per_gas_to_wei(base_fee_per_gas), burned_fee_counter)
- end
-
- defp base_fee_per_gas_to_wei(%Wei{} = wei), do: wei
- defp base_fee_per_gas_to_wei(base_fee_per_gas), do: %Wei{value: Decimal.new(base_fee_per_gas)}
-
- @uncle_reward_coef 1 / 32
- def block_reward_by_parts(block, transactions) do
- %{hash: block_hash, number: block_number} = block
- base_fee_per_gas = Map.get(block, :base_fee_per_gas)
-
- txn_fees = txn_fees(transactions)
-
- static_reward =
- Repo.one(
- from(
- er in EmissionReward,
- where: fragment("int8range(?, ?) <@ ?", ^block_number, ^(block_number + 1), er.block_range),
- select: er.reward
- )
- ) || %Wei{value: Decimal.new(0)}
-
- has_uncles? = is_list(block.uncles) and not Enum.empty?(block.uncles)
-
- burned_fees = burned_fees(transactions, base_fee_per_gas)
- uncle_reward = (has_uncles? && Wei.mult(static_reward, Decimal.from_float(@uncle_reward_coef))) || nil
-
- %{
- block_number: block_number,
- block_hash: block_hash,
- miner_hash: block.miner_hash,
- static_reward: static_reward,
- txn_fees: %Wei{value: txn_fees},
- burned_fees: burned_fees || %Wei{value: Decimal.new(0)},
- uncle_reward: uncle_reward || %Wei{value: Decimal.new(0)}
- }
- end
-
@doc """
The `t:Explorer.Chain.Wei.t/0` paid to the miners of the `t:Explorer.Chain.Block.t/0`s with `hash`
`Explorer.Chain.Hash.Full.t/0` by the signers of the transactions in those blocks to cover the gas fee
@@ -947,17 +533,34 @@ defmodule Explorer.Chain do
@spec gas_payment_by_block_hash([Hash.Full.t()]) :: %{Hash.Full.t() => Wei.t()}
def gas_payment_by_block_hash(block_hashes) when is_list(block_hashes) do
query =
- from(
- block in Block,
- left_join: transaction in assoc(block, :transactions),
- where: block.hash in ^block_hashes and block.consensus == true,
- group_by: block.hash,
- select: {block.hash, %Wei{value: coalesce(sum(transaction.gas_used * transaction.gas_price), 0)}}
- )
+ if DenormalizationHelper.denormalization_finished?() do
+ from(
+ transaction in Transaction,
+ where: transaction.block_hash in ^block_hashes and transaction.block_consensus == true,
+ group_by: transaction.block_hash,
+ select: {transaction.block_hash, %Wei{value: coalesce(sum(transaction.gas_used * transaction.gas_price), 0)}}
+ )
+ else
+ from(
+ block in Block,
+ left_join: transaction in assoc(block, :transactions),
+ where: block.hash in ^block_hashes and block.consensus == true,
+ group_by: block.hash,
+ select: {block.hash, %Wei{value: coalesce(sum(transaction.gas_used * transaction.gas_price), 0)}}
+ )
+ end
- query
- |> Repo.all()
- |> Enum.into(%{})
+ initial_gas_payments =
+ block_hashes
+ |> Enum.map(&{&1, %Wei{value: Decimal.new(0)}})
+ |> Enum.into(%{})
+
+ existing_data =
+ query
+ |> Repo.all()
+ |> Enum.into(%{})
+
+ Map.merge(initial_gas_payments, existing_data)
end
def timestamp_by_block_hash(block_hashes) when is_list(block_hashes) do
@@ -986,9 +589,10 @@ defmodule Explorer.Chain do
`:key` (a tuple of the lowest/oldest `{index}`) and. Results will be the transactions older than
the `index` that are passed.
"""
- @spec block_to_transactions(Hash.Full.t(), [paging_options | necessity_by_association_option], true | false) :: [
- Transaction.t()
- ]
+ @spec block_to_transactions(Hash.Full.t(), [paging_options | necessity_by_association_option | api?()], true | false) ::
+ [
+ Transaction.t()
+ ]
def block_to_transactions(block_hash, options \\ [], old_ui? \\ true) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
@@ -998,6 +602,7 @@ defmodule Explorer.Chain do
|> join(:inner, [transaction], block in assoc(transaction, :block))
|> where([_, block], block.hash == ^block_hash)
|> join_associations(necessity_by_association)
+ |> Transaction.put_has_token_transfers_to_tx(old_ui?)
|> (&if(old_ui?, do: preload(&1, [{:token_transfers, [:token, :from_address, :to_address]}]), else: &1)).()
|> select_repo(options).all()
|> (&if(old_ui?,
@@ -1007,6 +612,22 @@ defmodule Explorer.Chain do
)).()
end
+ @spec execution_node_to_transactions(Hash.Address.t(), [paging_options | necessity_by_association_option | api?()]) ::
+ [Transaction.t()]
+ def execution_node_to_transactions(execution_node_hash, options \\ []) when is_list(options) do
+ necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
+
+ options
+ |> Keyword.get(:paging_options, @default_paging_options)
+ |> fetch_transactions_in_descending_order_by_block_and_index()
+ |> where(execution_node_hash: ^execution_node_hash)
+ |> join_associations(necessity_by_association)
+ |> Transaction.put_has_token_transfers_to_tx(false)
+ |> (& &1).()
+ |> select_repo(options).all()
+ |> (&Enum.map(&1, fn tx -> preload_token_transfers(tx, @token_transfers_necessity_by_association, options) end)).()
+ end
+
@spec block_to_withdrawals(
Hash.Full.t(),
[paging_options | necessity_by_association_option]
@@ -1105,53 +726,6 @@ defmodule Explorer.Chain do
|> select_repo(options).exists?()
end
- @spec address_to_incoming_transaction_count(Hash.Address.t()) :: non_neg_integer()
- def address_to_incoming_transaction_count(address_hash) do
- to_address_query =
- from(
- transaction in Transaction,
- where: transaction.to_address_hash == ^address_hash
- )
-
- Repo.aggregate(to_address_query, :count, :hash, timeout: :infinity)
- end
-
- @spec address_hash_to_transaction_count(Hash.Address.t()) :: non_neg_integer()
- def address_hash_to_transaction_count(address_hash) do
- query =
- from(
- transaction in Transaction,
- where: transaction.to_address_hash == ^address_hash or transaction.from_address_hash == ^address_hash
- )
-
- Repo.aggregate(query, :count, :hash, timeout: :infinity)
- end
-
- @spec address_to_incoming_transaction_gas_usage(Hash.Address.t()) :: Decimal.t() | nil
- def address_to_incoming_transaction_gas_usage(address_hash) do
- to_address_query =
- from(
- transaction in Transaction,
- where: transaction.to_address_hash == ^address_hash
- )
-
- Repo.aggregate(to_address_query, :sum, :gas_used, timeout: :infinity)
- end
-
- @spec address_to_outcoming_transaction_gas_usage(Hash.Address.t()) :: Decimal.t() | nil
- def address_to_outcoming_transaction_gas_usage(address_hash) do
- to_address_query =
- from(
- transaction in Transaction,
- where: transaction.from_address_hash == ^address_hash
- )
-
- Repo.aggregate(to_address_query, :sum, :gas_used, timeout: :infinity)
- end
-
- @spec max_incoming_transactions_count() :: non_neg_integer()
- def max_incoming_transactions_count, do: @max_incoming_transactions_count
-
@doc """
How many blocks have confirmed `block` based on the current `max_block_number`
@@ -1253,6 +827,33 @@ defmodule Explorer.Chain do
end
end
+ defp set_address_decompiled(repo, address_hash) do
+ query =
+ from(
+ address in Address,
+ where: address.hash == ^address_hash
+ )
+
+ case repo.update_all(query, set: [decompiled: true]) do
+ {1, _} -> {:ok, []}
+ _ -> {:error, "There was an error annotating that the address has been decompiled."}
+ end
+ end
+
+ @spec verified_contracts_top(non_neg_integer()) :: [Hash.Address.t()]
+ def verified_contracts_top(limit) do
+ query =
+ from(contract in SmartContract,
+ inner_join: address in Address,
+ on: contract.address_hash == address.hash,
+ order_by: [desc: address.transactions_count],
+ limit: ^limit,
+ select: contract.address_hash
+ )
+
+ Repo.all(query)
+ end
+
@doc """
Converts the `Explorer.Chain.Data.t:t/0` to `iodata` representation that can be written to users efficiently.
@@ -1306,7 +907,9 @@ defmodule Explorer.Chain do
{:actual, Decimal.new(4)}
"""
- @spec fee(Transaction.t(), :ether | :gwei | :wei) :: {:maximum, Decimal.t()} | {:actual, Decimal.t()}
+ @spec fee(Transaction.t(), :ether | :gwei | :wei) :: {:maximum, Decimal.t()} | {:actual, Decimal.t() | nil}
+ def fee(%Transaction{gas: _gas, gas_price: nil, gas_used: nil}, _unit), do: {:maximum, nil}
+
def fee(%Transaction{gas: gas, gas_price: gas_price, gas_used: nil}, unit) do
fee =
gas_price
@@ -1316,6 +919,8 @@ defmodule Explorer.Chain do
{:maximum, fee}
end
+ def fee(%Transaction{gas_price: nil, gas_used: _gas_used}, _unit), do: {:actual, nil}
+
def fee(%Transaction{gas_price: gas_price, gas_used: gas_used}, unit) do
fee =
gas_price
@@ -1344,27 +949,46 @@ defmodule Explorer.Chain do
if variant == EthereumJSONRPC.Ganache || variant == EthereumJSONRPC.Arbitrum do
true
else
- with {:transactions_exist, true} <- {:transactions_exist, select_repo(options).exists?(Transaction)},
- min_block_number when not is_nil(min_block_number) <-
- select_repo(options).aggregate(Transaction, :min, :block_number) do
- min_block_number =
- min_block_number
- |> Decimal.max(EthereumJSONRPC.first_block_to_fetch(:trace_first_block))
- |> Decimal.to_integer()
-
- query =
- from(
- block in Block,
- join: pending_ops in assoc(block, :pending_operations),
- where: block.consensus and block.number == ^min_block_number
- )
+ check_left_blocks_to_index_internal_transactions(options)
+ end
+ end
+ end
- !select_repo(options).exists?(query)
- else
- {:transactions_exist, false} -> true
- nil -> false
- end
+ defp check_left_blocks_to_index_internal_transactions(options) do
+ with {:transactions_exist, true} <- {:transactions_exist, select_repo(options).exists?(Transaction)},
+ min_block_number when not is_nil(min_block_number) <-
+ select_repo(options).aggregate(Transaction, :min, :block_number) do
+ min_block_number =
+ min_block_number
+ |> Decimal.max(Application.get_env(:indexer, :trace_first_block))
+ |> Decimal.to_integer()
+
+ query =
+ from(
+ block in Block,
+ join: pending_ops in assoc(block, :pending_operations),
+ where: block.consensus and block.number == ^min_block_number
+ )
+
+ if select_repo(options).exists?(query) do
+ false
+ else
+ check_indexing_internal_transactions_threshold()
end
+ else
+ {:transactions_exist, false} -> true
+ nil -> false
+ end
+ end
+
+ defp check_indexing_internal_transactions_threshold do
+ pbo_count = PendingBlockOperationCache.estimated_count()
+
+ if pbo_count <
+ Application.get_env(:indexer, Indexer.Fetcher.InternalTransaction)[:indexing_finished_threshold] do
+ true
+ else
+ false
end
end
@@ -1406,468 +1030,106 @@ defmodule Explorer.Chain do
...> )
iex> {:ok, %Explorer.Chain.Address{hash: found_hash}} = Explorer.Chain.hash_to_address(hash)
iex> found_hash == hash
- true
-
- Returns `{:error, :not_found}` if not found
-
- iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed")
- iex> Explorer.Chain.hash_to_address(hash)
- {:error, :not_found}
-
- ## Options
-
- * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
- `:required`, and the `t:Explorer.Chain.Address.t/0` has no associated record for that association,
- then the `t:Explorer.Chain.Address.t/0` will not be included in the list.
-
- Optionally it also accepts a boolean to fetch the `has_decompiled_code?` virtual field or not
-
- """
- @spec hash_to_address(Hash.Address.t(), [necessity_by_association_option | api?], boolean()) ::
- {:ok, Address.t()} | {:error, :not_found}
- def hash_to_address(
- %Hash{byte_count: unquote(Hash.Address.byte_count())} = hash,
- options \\ [
- necessity_by_association: %{
- :contracts_creation_internal_transaction => :optional,
- :names => :optional,
- :smart_contract => :optional,
- :token => :optional,
- :contracts_creation_transaction => :optional
- }
- ],
- query_decompiled_code_flag \\ true
- ) do
- necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
-
- query =
- from(
- address in Address,
- where: address.hash == ^hash
- )
-
- address_result =
- query
- |> join_associations(necessity_by_association)
- |> with_decompiled_code_flag(hash, query_decompiled_code_flag)
- |> select_repo(options).one()
-
- address_updated_result =
- case address_result do
- %{smart_contract: smart_contract} ->
- if smart_contract do
- address_result
- else
- compose_smart_contract(address_result, hash, options)
- end
-
- _ ->
- address_result
- end
-
- address_updated_result
- |> case do
- nil -> {:error, :not_found}
- address -> {:ok, address}
- end
- end
-
- defp compose_smart_contract(address_result, hash, options) do
- address_verified_twin_contract =
- get_minimal_proxy_template(hash, options) ||
- get_address_verified_twin_contract(hash, options).verified_contract
-
- if address_verified_twin_contract do
- address_verified_twin_contract_updated =
- address_verified_twin_contract
- |> Map.put(:address_hash, hash)
- |> Map.put(:metadata_from_verified_twin, true)
- |> Map.put(:implementation_address_hash, nil)
- |> Map.put(:implementation_name, nil)
- |> Map.put(:implementation_fetched_at, nil)
-
- address_result
- |> Map.put(:smart_contract, address_verified_twin_contract_updated)
- else
- address_result
- end
- end
-
- def decompiled_code(address_hash, version) do
- query =
- from(contract in DecompiledSmartContract,
- where: contract.address_hash == ^address_hash and contract.decompiler_version == ^version
- )
-
- query
- |> Repo.one()
- |> case do
- nil -> {:error, :not_found}
- contract -> {:ok, contract.decompiled_source_code}
- end
- end
-
- @spec token_contract_address_from_token_name(String.t()) :: {:ok, Hash.Address.t()} | {:error, :not_found}
- def token_contract_address_from_token_name(name) when is_binary(name) do
- query =
- from(token in Token,
- where: ilike(token.symbol, ^name),
- or_where: ilike(token.name, ^name),
- select: token.contract_address_hash
- )
-
- query
- |> Repo.all()
- |> case do
- [] ->
- {:error, :not_found}
-
- hashes ->
- if Enum.count(hashes) == 1 do
- {:ok, List.first(hashes)}
- else
- {:error, :not_found}
- end
- end
- end
-
- defp prepare_search_term(string) do
- case Regex.scan(~r/[a-zA-Z0-9]+/, string) do
- [_ | _] = words ->
- term_final =
- words
- |> Enum.map_join(" & ", fn [word] -> word <> ":*" end)
-
- {:some, term_final}
-
- _ ->
- :none
- end
- end
-
- defp search_token_query(term) do
- from(token in Token,
- where: fragment("to_tsvector(symbol || ' ' || name ) @@ to_tsquery(?)", ^term),
- select: %{
- address_hash: token.contract_address_hash,
- tx_hash: fragment("CAST(NULL AS bytea)"),
- block_hash: fragment("CAST(NULL AS bytea)"),
- type: "token",
- name: token.name,
- symbol: token.symbol,
- holder_count: token.holder_count,
- inserted_at: token.inserted_at,
- block_number: 0
- }
- )
- end
-
- defp search_contract_query(term) do
- from(smart_contract in SmartContract,
- left_join: address in Address,
- on: smart_contract.address_hash == address.hash,
- where: fragment("to_tsvector(name ) @@ to_tsquery(?)", ^term),
- select: %{
- address_hash: smart_contract.address_hash,
- tx_hash: fragment("CAST(NULL AS bytea)"),
- block_hash: fragment("CAST(NULL AS bytea)"),
- type: "contract",
- name: smart_contract.name,
- symbol: ^nil,
- holder_count: ^nil,
- inserted_at: address.inserted_at,
- block_number: 0
- }
- )
- end
-
- defp search_address_query(term) do
- case Chain.string_to_address_hash(term) do
- {:ok, address_hash} ->
- from(address in Address,
- left_join:
- address_name in subquery(
- from(name in Address.Name,
- where: name.address_hash == ^address_hash,
- order_by: [desc: name.primary],
- limit: 1
- )
- ),
- on: address.hash == address_name.address_hash,
- where: address.hash == ^address_hash,
- select: %{
- address_hash: address.hash,
- tx_hash: fragment("CAST(NULL AS bytea)"),
- block_hash: fragment("CAST(NULL AS bytea)"),
- type: "address",
- name: address_name.name,
- symbol: ^nil,
- holder_count: ^nil,
- inserted_at: address.inserted_at,
- block_number: 0
- }
- )
-
- _ ->
- nil
- end
- end
-
- defp search_tx_query(term) do
- case Chain.string_to_transaction_hash(term) do
- {:ok, tx_hash} ->
- from(transaction in Transaction,
- where: transaction.hash == ^tx_hash,
- select: %{
- address_hash: fragment("CAST(NULL AS bytea)"),
- tx_hash: transaction.hash,
- block_hash: fragment("CAST(NULL AS bytea)"),
- type: "transaction",
- name: ^nil,
- symbol: ^nil,
- holder_count: ^nil,
- inserted_at: transaction.inserted_at,
- block_number: 0
- }
- )
-
- _ ->
- nil
- end
- end
-
- defp search_block_query(term) do
- case Chain.string_to_block_hash(term) do
- {:ok, block_hash} ->
- from(block in Block,
- where: block.hash == ^block_hash,
- select: %{
- address_hash: fragment("CAST(NULL AS bytea)"),
- tx_hash: fragment("CAST(NULL AS bytea)"),
- block_hash: block.hash,
- type: "block",
- name: ^nil,
- symbol: ^nil,
- holder_count: ^nil,
- inserted_at: block.inserted_at,
- block_number: block.number
- }
- )
-
- _ ->
- case Integer.parse(term) do
- {block_number, ""} ->
- from(block in Block,
- where: block.number == ^block_number,
- select: %{
- address_hash: fragment("CAST(NULL AS bytea)"),
- tx_hash: fragment("CAST(NULL AS bytea)"),
- block_hash: block.hash,
- type: "block",
- name: ^nil,
- symbol: ^nil,
- holder_count: ^nil,
- inserted_at: block.inserted_at,
- block_number: block.number
- }
- )
-
- _ ->
- nil
- end
- end
- end
-
- def joint_search(paging_options, offset, raw_string, options \\ []) do
- string = String.trim(raw_string)
-
- case prepare_search_term(string) do
- {:some, term} ->
- tokens_query = search_token_query(term)
- contracts_query = search_contract_query(term)
- tx_query = search_tx_query(string)
- address_query = search_address_query(string)
- block_query = search_block_query(string)
-
- basic_query =
- from(
- tokens in subquery(tokens_query),
- union: ^contracts_query
- )
-
- query =
- cond do
- address_query ->
- basic_query
- |> union(^address_query)
-
- tx_query ->
- basic_query
- |> union(^tx_query)
- |> union(^block_query)
-
- block_query ->
- basic_query
- |> union(^block_query)
-
- true ->
- basic_query
- end
-
- ordered_query =
- from(items in subquery(query),
- order_by: [desc_nulls_last: items.holder_count, asc: items.name, desc: items.inserted_at],
- limit: ^paging_options.page_size,
- offset: ^offset
- )
-
- paginated_ordered_query =
- ordered_query
- |> page_search_results(paging_options)
-
- search_results = select_repo(options).all(paginated_ordered_query)
-
- search_results
- |> Enum.map(fn result ->
- compose_result_checksummed_address_hash(result)
- end)
+ true
- _ ->
- []
- end
- end
+ Returns `{:error, :not_found}` if not found
- defp compose_result_checksummed_address_hash(result) do
- if result.address_hash do
- result
- |> Map.put(:address_hash, Address.checksum(result.address_hash))
- else
- result
- end
- end
+ iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed")
+ iex> Explorer.Chain.hash_to_address(hash)
+ {:error, :not_found}
- @spec search_token(String.t()) :: [Token.t()]
- def search_token(string) do
- case prepare_search_term(string) do
- {:some, term} ->
- query =
- from(token in Token,
- where: fragment("to_tsvector(symbol || ' ' || name ) @@ to_tsquery(?)", ^term),
- select: %{
- link: token.contract_address_hash,
- symbol: token.symbol,
- name: token.name,
- holder_count: token.holder_count,
- type: "token"
- },
- order_by: [desc: token.holder_count]
- )
+ ## Options
- Repo.all(query)
+ * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
+ `:required`, and the `t:Explorer.Chain.Address.t/0` has no associated record for that association,
+ then the `t:Explorer.Chain.Address.t/0` will not be included in the list.
- _ ->
- []
- end
- end
+ Optionally it also accepts a boolean to fetch the `has_decompiled_code?` virtual field or not
- @spec search_contract(String.t()) :: [SmartContract.t()]
- def search_contract(string) do
- case prepare_search_term(string) do
- {:some, term} ->
- query =
- from(smart_contract in SmartContract,
- left_join: address in Address,
- on: smart_contract.address_hash == address.hash,
- where: fragment("to_tsvector(name ) @@ to_tsquery(?)", ^term),
- select: %{
- link: smart_contract.address_hash,
- name: smart_contract.name,
- inserted_at: address.inserted_at,
- type: "contract"
- },
- order_by: [desc: smart_contract.inserted_at]
- )
+ """
+ @spec hash_to_address(Hash.Address.t() | binary(), [necessity_by_association_option | api?], boolean()) ::
+ {:ok, Address.t()} | {:error, :not_found}
+ def hash_to_address(
+ hash,
+ options \\ [
+ necessity_by_association: %{
+ :contracts_creation_internal_transaction => :optional,
+ :names => :optional,
+ :smart_contract => :optional,
+ :token => :optional,
+ :contracts_creation_transaction => :optional
+ }
+ ],
+ query_decompiled_code_flag \\ true
+ ) do
+ necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
- Repo.all(query)
+ query =
+ from(
+ address in Address,
+ where: address.hash == ^hash
+ )
- _ ->
- []
- end
- end
+ address_result =
+ query
+ |> join_associations(necessity_by_association)
+ |> with_decompiled_code_flag(hash, query_decompiled_code_flag)
+ |> select_repo(options).one()
- def search_tx(term) do
- case Chain.string_to_transaction_hash(term) do
- {:ok, tx_hash} ->
- query =
- from(transaction in Transaction,
- where: transaction.hash == ^tx_hash,
- select: %{
- link: transaction.hash,
- type: "transaction"
- }
- )
+ address_updated_result =
+ case address_result do
+ %{smart_contract: smart_contract} ->
+ if smart_contract do
+ address_result
+ else
+ SmartContract.compose_smart_contract(address_result, hash, options)
+ end
- Repo.all(query)
+ _ ->
+ address_result
+ end
- _ ->
- []
+ address_updated_result
+ |> case do
+ nil -> {:error, :not_found}
+ address -> {:ok, address}
end
end
- def search_address(term) do
- case Chain.string_to_address_hash(term) do
- {:ok, address_hash} ->
- query =
- from(address in Address,
- left_join: address_name in Address.Name,
- on: address.hash == address_name.address_hash,
- where: address.hash == ^address_hash,
- select: %{
- name: address_name.name,
- link: address.hash,
- type: "address"
- }
- )
-
- Repo.all(query)
+ def decompiled_code(address_hash, version) do
+ query =
+ from(contract in DecompiledSmartContract,
+ where: contract.address_hash == ^address_hash and contract.decompiler_version == ^version
+ )
- _ ->
- []
+ query
+ |> Repo.one()
+ |> case do
+ nil -> {:error, :not_found}
+ contract -> {:ok, contract.decompiled_source_code}
end
end
- def search_block(term) do
- case Chain.string_to_block_hash(term) do
- {:ok, block_hash} ->
- query =
- from(block in Block,
- where: block.hash == ^block_hash,
- select: %{
- link: block.hash,
- block_number: block.number,
- type: "block"
- }
- )
-
- Repo.all(query)
-
- _ ->
- case Integer.parse(term) do
- {block_number, _} ->
- query =
- from(block in Block,
- where: block.number == ^block_number,
- select: %{
- link: block.hash,
- block_number: block.number,
- type: "block"
- }
- )
+ @spec token_contract_address_from_token_name(String.t()) :: {:ok, Hash.Address.t()} | {:error, :not_found}
+ def token_contract_address_from_token_name(name) when is_binary(name) do
+ query =
+ from(token in Token,
+ where: ilike(token.symbol, ^name),
+ or_where: ilike(token.name, ^name),
+ select: token.contract_address_hash
+ )
- Repo.all(query)
+ query
+ |> Repo.all()
+ |> case do
+ [] ->
+ {:error, :not_found}
- _ ->
- []
+ hashes ->
+ if Enum.count(hashes) == 1 do
+ {:ok, List.first(hashes)}
+ else
+ {:error, :not_found}
end
end
end
@@ -1991,15 +1253,15 @@ defmodule Explorer.Chain do
if smart_contract do
CheckBytecodeMatchingOnDemand.trigger_check(address_result, smart_contract)
LookUpSmartContractSourcesOnDemand.trigger_fetch(address_result, smart_contract)
- address_result
+ SmartContract.check_and_update_constructor_args(address_result)
else
LookUpSmartContractSourcesOnDemand.trigger_fetch(address_result, nil)
address_verified_twin_contract =
- get_minimal_proxy_template(hash, options) ||
- get_address_verified_twin_contract(hash, options).verified_contract
+ EIP1167.get_implementation_address(hash, options) ||
+ SmartContract.get_address_verified_twin_contract(hash, options).verified_contract
- add_twin_info_to_contract(address_result, address_verified_twin_contract, hash)
+ SmartContract.add_twin_info_to_contract(address_result, address_verified_twin_contract, hash)
end
_ ->
@@ -2014,23 +1276,6 @@ defmodule Explorer.Chain do
end
end
- defp add_twin_info_to_contract(address_result, address_verified_twin_contract, _hash)
- when is_nil(address_verified_twin_contract),
- do: address_result
-
- defp add_twin_info_to_contract(address_result, address_verified_twin_contract, hash) do
- address_verified_twin_contract_updated =
- address_verified_twin_contract
- |> Map.put(:address_hash, hash)
- |> Map.put(:metadata_from_verified_twin, true)
- |> Map.put(:implementation_address_hash, nil)
- |> Map.put(:implementation_name, nil)
- |> Map.put(:implementation_fetched_at, nil)
-
- address_result
- |> Map.put(:smart_contract, address_verified_twin_contract_updated)
- end
-
@spec find_decompiled_contract_address(Hash.Address.t()) :: {:ok, Address.t()} | {:error, :not_found}
def find_decompiled_contract_address(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do
query =
@@ -2182,27 +1427,31 @@ defmodule Explorer.Chain do
options,
preload_to_detect_tt? \\ true
) do
- limit = if(preload_to_detect_tt?, do: 1, else: @token_transfers_per_transaction_preview + 1)
-
- token_transfers =
- TokenTransfer
- |> (&if(is_nil(block_hash),
- do: where(&1, [token_transfer], token_transfer.transaction_hash == ^tx_hash),
- else:
- where(
- &1,
- [token_transfer],
- token_transfer.transaction_hash == ^tx_hash and token_transfer.block_hash == ^block_hash
- )
- )).()
- |> limit(^limit)
- |> order_by([token_transfer], asc: token_transfer.log_index)
- |> join_associations(necessity_by_association)
- |> select_repo(options).all()
- |> flat_1155_batch_token_transfers()
- |> Enum.take(limit)
-
- %Transaction{transaction | token_transfers: token_transfers}
+ if preload_to_detect_tt? do
+ transaction
+ else
+ limit = if(preload_to_detect_tt?, do: 1, else: @token_transfers_per_transaction_preview + 1)
+
+ token_transfers =
+ TokenTransfer
+ |> (&if(is_nil(block_hash),
+ do: where(&1, [token_transfer], token_transfer.transaction_hash == ^tx_hash),
+ else:
+ where(
+ &1,
+ [token_transfer],
+ token_transfer.transaction_hash == ^tx_hash and token_transfer.block_hash == ^block_hash
+ )
+ )).()
+ |> limit(^limit)
+ |> order_by([token_transfer], asc: token_transfer.log_index)
+ |> (&if(preload_to_detect_tt?, do: &1, else: join_associations(&1, necessity_by_association))).()
+ |> select_repo(options).all()
+ |> flat_1155_batch_token_transfers()
+ |> Enum.take(limit)
+
+ %Transaction{transaction | token_transfers: token_transfers}
+ end
end
def get_token_transfers_per_transaction_preview_count, do: @token_transfers_per_transaction_preview
@@ -2235,15 +1484,15 @@ defmodule Explorer.Chain do
`:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the
`t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`.
"""
- @spec hashes_to_transactions([Hash.Full.t()], [necessity_by_association_option]) :: [Transaction.t()] | []
+ @spec hashes_to_transactions([Hash.Full.t()], [necessity_by_association_option | api?]) :: [Transaction.t()] | []
def hashes_to_transactions(hashes, options \\ []) when is_list(hashes) and is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
- fetch_transactions()
+ Transaction.fetch_transactions()
|> where([transaction], transaction.hash in ^hashes)
|> join_associations(necessity_by_association)
|> preload([{:token_transfers, [:token, :from_address, :to_address]}])
- |> Repo.all()
+ |> select_repo(options).all()
end
@doc """
@@ -2270,15 +1519,18 @@ defmodule Explorer.Chain do
if Application.get_env(:indexer, Indexer.Supervisor)[:enabled] do
%{min: min_saved_block_number, max: max_saved_block_number} = BlockNumber.get_all()
- min_blockchain_block_number = min_block_number_from_config(:first_block)
+ min_blockchain_block_number = Application.get_env(:indexer, :first_block)
case {min_saved_block_number, max_saved_block_number} do
{0, 0} ->
Decimal.new(0)
_ ->
- BlockCache.estimated_count()
- |> Decimal.div(max_saved_block_number - min_blockchain_block_number + 1)
+ divisor = max_saved_block_number - min_blockchain_block_number + 1
+
+ ratio = get_ratio(BlockCache.estimated_count(), divisor)
+
+ ratio
|> (&if(
greater_or_equal_0_99(&1) &&
min_saved_block_number <= min_blockchain_block_number,
@@ -2297,9 +1549,9 @@ defmodule Explorer.Chain do
if Application.get_env(:indexer, Indexer.Supervisor)[:enabled] &&
not Application.get_env(:indexer, Indexer.Fetcher.InternalTransaction.Supervisor)[:disabled?] do
%{max: max_saved_block_number} = BlockNumber.get_all()
- pbo_count = Repo.aggregate(PendingBlockOperation, :count, timeout: :infinity)
+ pbo_count = PendingBlockOperationCache.estimated_count()
- min_blockchain_trace_block_number = min_block_number_from_config(:trace_first_block)
+ min_blockchain_trace_block_number = Application.get_env(:indexer, :trace_first_block)
case max_saved_block_number do
0 ->
@@ -2307,10 +1559,11 @@ defmodule Explorer.Chain do
_ ->
full_blocks_range = max_saved_block_number - min_blockchain_trace_block_number + 1
- processed_int_txs_for_blocks_count = full_blocks_range - pbo_count
+ processed_int_txs_for_blocks_count = max(0, full_blocks_range - pbo_count)
+
+ ratio = get_ratio(processed_int_txs_for_blocks_count, full_blocks_range)
- processed_int_txs_for_blocks_count
- |> Decimal.div(full_blocks_range)
+ ratio
|> (&if(
greater_or_equal_0_99(&1),
do: Decimal.new(1),
@@ -2323,20 +1576,19 @@ defmodule Explorer.Chain do
end
end
+ @spec get_ratio(non_neg_integer(), non_neg_integer()) :: Decimal.t()
+ defp get_ratio(dividend, divisor) do
+ if divisor > 0,
+ do: dividend |> Decimal.div(divisor),
+ else: Decimal.new(1)
+ end
+
@spec greater_or_equal_0_99(Decimal.t()) :: boolean()
defp greater_or_equal_0_99(value) do
Decimal.compare(value, Decimal.from_float(0.99)) == :gt ||
Decimal.compare(value, Decimal.from_float(0.99)) == :eq
end
- @spec min_block_number_from_config(atom()) :: integer()
- defp min_block_number_from_config(block_type) do
- case Integer.parse(Application.get_env(:indexer, block_type)) do
- {block_number, _} -> block_number
- _ -> 0
- end
- end
-
@spec format_indexed_ratio(Decimal.t()) :: Decimal.t()
defp format_indexed_ratio(raw_ratio) do
raw_ratio
@@ -2503,122 +1755,12 @@ defmodule Explorer.Chain do
|> Enum.into(%{})
end
- @doc """
- Lists the top `t:Explorer.Chain.Address.t/0`'s' in descending order based on coin balance and address hash.
-
- """
- @spec list_top_addresses :: [{Address.t(), non_neg_integer()}]
- def list_top_addresses(options \\ []) do
- paging_options = Keyword.get(options, :paging_options, @default_paging_options)
-
- if is_nil(paging_options.key) do
- paging_options.page_size
- |> Accounts.take_enough()
- |> case do
- nil ->
- get_addresses(options)
-
- accounts ->
- Enum.map(
- accounts,
- &{&1,
- if is_nil(&1.nonce) do
- 0
- else
- &1.nonce + 1
- end}
- )
- end
- else
- fetch_top_addresses(options)
- end
- end
-
- defp get_addresses(options) do
- accounts_with_n = fetch_top_addresses(options)
-
- accounts_with_n
- |> Enum.map(fn {address, _n} -> address end)
- |> Accounts.update()
-
- accounts_with_n
- end
-
- defp fetch_top_addresses(options) do
- paging_options = Keyword.get(options, :paging_options, @default_paging_options)
-
- base_query =
- from(a in Address,
- where: a.fetched_coin_balance > ^0,
- order_by: [desc: a.fetched_coin_balance, asc: a.hash],
- preload: [:names],
- select: {a, fragment("coalesce(1 + ?, 0)", a.nonce)}
- )
-
- base_query
- |> page_addresses(paging_options)
- |> limit(^paging_options.page_size)
- |> select_repo(options).all()
- end
-
- @doc """
- Lists the top `t:Explorer.Chain.Token.t/0`'s'.
-
- """
- @spec list_top_tokens(String.t()) :: [{Token.t(), non_neg_integer()}]
- def list_top_tokens(filter, options \\ []) do
- paging_options = Keyword.get(options, :paging_options, @default_paging_options)
- token_type = Keyword.get(options, :token_type, nil)
-
- fetch_top_tokens(filter, paging_options, token_type, options)
- end
-
- defp fetch_top_tokens(filter, paging_options, token_type, options) do
- base_query = base_token_query(token_type)
-
- base_query_with_paging =
- base_query
- |> page_tokens(paging_options)
- |> limit(^paging_options.page_size)
-
- query =
- if filter && filter !== "" do
- case prepare_search_term(filter) do
- {:some, filter_term} ->
- base_query_with_paging
- |> where(fragment("to_tsvector('english', symbol || ' ' || name ) @@ to_tsquery(?)", ^filter_term))
-
- _ ->
- base_query_with_paging
- end
- else
- base_query_with_paging
- end
-
- query
- |> select_repo(options).all()
- end
-
- defp base_token_query(empty_type) when empty_type in [nil, []] do
- from(t in Token,
- order_by: [desc_nulls_last: t.circulating_market_cap, desc_nulls_last: t.holder_count, asc: t.name],
- preload: [:contract_address]
- )
- end
-
- defp base_token_query(token_types) when is_list(token_types) do
- from(t in Token,
- where: t.type in ^token_types,
- order_by: [desc_nulls_last: t.circulating_market_cap, desc_nulls_last: t.holder_count, asc: t.name],
- preload: [:contract_address]
- )
- end
-
@doc """
Calls `reducer` on a stream of `t:Explorer.Chain.Block.t/0` without `t:Explorer.Chain.Block.Reward.t/0`.
"""
- def stream_blocks_without_rewards(initial, reducer) when is_function(reducer, 2) do
+ def stream_blocks_without_rewards(initial, reducer, limited? \\ false) when is_function(reducer, 2) do
Block.blocks_without_reward_query()
+ |> add_fetcher_limit(limited?)
|> Repo.stream_reduce(initial, reducer)
end
@@ -2661,56 +1803,6 @@ defmodule Explorer.Chain do
|> select_repo(options).all()
end
- def check_if_validated_blocks_at_address(address_hash, options \\ []) do
- select_repo(options).exists?(from(b in Block, where: b.miner_hash == ^address_hash))
- end
-
- def check_if_logs_at_address(address_hash, options \\ []) do
- select_repo(options).exists?(from(l in Log, where: l.address_hash == ^address_hash))
- end
-
- def check_if_internal_transactions_at_address(address_hash) do
- internal_transactions_exists_by_created_contract_address_hash =
- Repo.exists?(from(it in InternalTransaction, where: it.created_contract_address_hash == ^address_hash))
-
- internal_transactions_exists_by_from_address_hash =
- Repo.exists?(from(it in InternalTransaction, where: it.from_address_hash == ^address_hash))
-
- internal_transactions_exists_by_to_address_hash =
- Repo.exists?(from(it in InternalTransaction, where: it.to_address_hash == ^address_hash))
-
- internal_transactions_exists_by_created_contract_address_hash || internal_transactions_exists_by_from_address_hash ||
- internal_transactions_exists_by_to_address_hash
- end
-
- def check_if_token_transfers_at_address(address_hash, options \\ []) do
- token_transfers_exists_by_from_address_hash =
- select_repo(options).exists?(from(tt in TokenTransfer, where: tt.from_address_hash == ^address_hash))
-
- token_transfers_exists_by_to_address_hash =
- select_repo(options).exists?(from(tt in TokenTransfer, where: tt.to_address_hash == ^address_hash))
-
- token_transfers_exists_by_from_address_hash ||
- token_transfers_exists_by_to_address_hash
- end
-
- def check_if_tokens_at_address(address_hash, options \\ []) do
- select_repo(options).exists?(
- from(
- tb in CurrentTokenBalance,
- where: tb.address_hash == ^address_hash,
- where: tb.value > 0
- )
- )
- end
-
- @spec check_if_withdrawals_at_address(Hash.Address.t()) :: boolean()
- def check_if_withdrawals_at_address(address_hash, options \\ []) do
- address_hash
- |> Withdrawal.address_hash_to_withdrawals_unordered_query()
- |> select_repo(options).exists?()
- end
-
@doc """
Counts all of the block validations and groups by the `miner_hash`.
"""
@@ -2728,78 +1820,24 @@ defmodule Explorer.Chain do
end
@doc """
- Counts the number of `t:Explorer.Chain.Block.t/0` validated by the address with the given `hash`.
+ Return the balance in usd corresponding to this token. Return nil if the fiat_value of the token is not present.
"""
- @spec address_to_validation_count(Hash.Address.t(), [api?]) :: non_neg_integer()
- def address_to_validation_count(hash, options) do
- query = from(block in Block, where: block.miner_hash == ^hash, select: fragment("COUNT(*)"))
-
- select_repo(options).one(query)
- end
-
- @spec address_to_transaction_count(Address.t()) :: non_neg_integer()
- def address_to_transaction_count(address) do
- address_hash_to_transaction_count(address.hash)
- end
-
- @spec address_to_token_transfer_count(Address.t()) :: non_neg_integer()
- def address_to_token_transfer_count(address) do
- query =
- from(
- token_transfer in TokenTransfer,
- where: token_transfer.to_address_hash == ^address.hash,
- or_where: token_transfer.from_address_hash == ^address.hash
- )
-
- Repo.aggregate(query, :count, timeout: :infinity)
- end
-
- @spec address_to_gas_usage_count(Address.t()) :: Decimal.t() | nil
- def address_to_gas_usage_count(address) do
- if contract?(address) do
- incoming_transaction_gas_usage = address_to_incoming_transaction_gas_usage(address.hash)
-
- cond do
- !incoming_transaction_gas_usage ->
- address_to_outcoming_transaction_gas_usage(address.hash)
-
- Decimal.compare(incoming_transaction_gas_usage, 0) == :eq ->
- address_to_outcoming_transaction_gas_usage(address.hash)
-
- true ->
- incoming_transaction_gas_usage
- end
- else
- address_to_outcoming_transaction_gas_usage(address.hash)
- end
+ def balance_in_fiat(%{fiat_value: fiat_value} = token_balance) when not is_nil(fiat_value) do
+ token_balance.fiat_value
end
- @doc """
- Return the balance in usd corresponding to this token. Return nil if the fiat_value of the token is not present.
- """
- def balance_in_fiat(_token_balance, %{fiat_value: fiat_value, decimals: decimals})
- when nil in [fiat_value, decimals] do
+ def balance_in_fiat(%{token: %{fiat_value: fiat_value, decimals: decimals}}) when nil in [fiat_value, decimals] do
nil
end
- def balance_in_fiat(token_balance, %{fiat_value: fiat_value, decimals: decimals}) do
+ def balance_in_fiat(%{token: %{fiat_value: fiat_value, decimals: decimals}} = token_balance) do
tokens = CurrencyHelper.divide_decimals(token_balance.value, decimals)
Decimal.mult(tokens, fiat_value)
end
- def balance_in_fiat(%{token: %{fiat_value: nil}}) do
- nil
- end
-
- def balance_in_fiat(token_balance) do
- tokens = CurrencyHelper.divide_decimals(token_balance.value, token_balance.token.decimals)
- price = token_balance.token.fiat_value
- Decimal.mult(tokens, price)
- end
-
- defp contract?(%{contract_code: nil}), do: false
+ def contract?(%{contract_code: nil}), do: false
- defp contract?(%{contract_code: _}), do: true
+ def contract?(%{contract_code: _}), do: true
@doc """
Returns a stream of unfetched `t:Explorer.Chain.Address.CoinBalance.t/0`.
@@ -2828,10 +1866,11 @@ defmodule Explorer.Chain do
@spec stream_unfetched_balances(
initial :: accumulator,
reducer ::
- (entry :: %{address_hash: Hash.Address.t(), block_number: Block.block_number()}, accumulator -> accumulator)
+ (entry :: %{address_hash: Hash.Address.t(), block_number: Block.block_number()}, accumulator -> accumulator),
+ limited? :: boolean()
) :: {:ok, accumulator}
when accumulator: term()
- def stream_unfetched_balances(initial, reducer) when is_function(reducer, 2) do
+ def stream_unfetched_balances(initial, reducer, limited? \\ false) when is_function(reducer, 2) do
query =
from(
balance in CoinBalance,
@@ -2839,7 +1878,9 @@ defmodule Explorer.Chain do
select: %{address_hash: balance.address_hash, block_number: balance.block_number}
)
- Repo.stream_reduce(query, initial, reducer)
+ query
+ |> add_coin_balances_fetcher_limit(limited?)
+ |> Repo.stream_reduce(initial, reducer)
end
@doc """
@@ -2847,11 +1888,13 @@ defmodule Explorer.Chain do
"""
@spec stream_unfetched_token_balances(
initial :: accumulator,
- reducer :: (entry :: TokenBalance.t(), accumulator -> accumulator)
+ reducer :: (entry :: TokenBalance.t(), accumulator -> accumulator),
+ limited? :: boolean()
) :: {:ok, accumulator}
when accumulator: term()
- def stream_unfetched_token_balances(initial, reducer) when is_function(reducer, 2) do
+ def stream_unfetched_token_balances(initial, reducer, limited? \\ false) when is_function(reducer, 2) do
TokenBalance.unfetched_token_balances()
+ |> add_token_balances_fetcher_limit(limited?)
|> Repo.stream_reduce(initial, reducer)
end
@@ -2873,10 +1916,12 @@ defmodule Explorer.Chain do
"""
@spec stream_blocks_with_unfetched_internal_transactions(
initial :: accumulator,
- reducer :: (entry :: term(), accumulator -> accumulator)
+ reducer :: (entry :: term(), accumulator -> accumulator),
+ limited? :: boolean()
) :: {:ok, accumulator}
when accumulator: term()
- def stream_blocks_with_unfetched_internal_transactions(initial, reducer) when is_function(reducer, 2) do
+ def stream_blocks_with_unfetched_internal_transactions(initial, reducer, limited? \\ false)
+ when is_function(reducer, 2) do
query =
from(
po in PendingBlockOperation,
@@ -2884,7 +1929,9 @@ defmodule Explorer.Chain do
select: po.block_number
)
- Repo.stream_reduce(query, initial, reducer)
+ query
+ |> add_fetcher_limit(limited?)
+ |> Repo.stream_reduce(initial, reducer)
end
def remove_nonconsensus_blocks_from_pending_ops(block_hashes) do
@@ -2931,20 +1978,23 @@ defmodule Explorer.Chain do
| :value
],
initial :: accumulator,
- reducer :: (entry :: term(), accumulator -> accumulator)
+ reducer :: (entry :: term(), accumulator -> accumulator),
+ limited? :: boolean()
) :: {:ok, accumulator}
when accumulator: term()
- def stream_transactions_with_unfetched_created_contract_codes(fields, initial, reducer)
+ def stream_transactions_with_unfetched_created_contract_codes(fields, initial, reducer, limited? \\ false)
when is_function(reducer, 2) do
query =
from(t in Transaction,
where:
not is_nil(t.block_hash) and not is_nil(t.created_contract_address_hash) and
- is_nil(t.created_contract_code_indexed_at),
+ is_nil(t.created_contract_code_indexed_at) and t.status == ^1,
select: ^fields
)
- Repo.stream_reduce(query, initial, reducer)
+ query
+ |> add_fetcher_limit(limited?)
+ |> Repo.stream_reduce(initial, reducer)
end
@spec stream_mined_transactions(
@@ -2996,14 +2046,16 @@ defmodule Explorer.Chain do
| :value
],
initial :: accumulator,
- reducer :: (entry :: term(), accumulator -> accumulator)
+ reducer :: (entry :: term(), accumulator -> accumulator),
+ limited? :: boolean()
) :: {:ok, accumulator}
when accumulator: term()
- def stream_pending_transactions(fields, initial, reducer) when is_function(reducer, 2) do
+ def stream_pending_transactions(fields, initial, reducer, limited? \\ false) when is_function(reducer, 2) do
query =
Transaction
|> pending_transactions_query()
|> select(^fields)
+ |> add_fetcher_limit(limited?)
Repo.stream_reduce(query, initial, reducer)
end
@@ -3018,17 +2070,20 @@ defmodule Explorer.Chain do
"""
@spec stream_unfetched_uncles(
initial :: accumulator,
- reducer :: (entry :: term(), accumulator -> accumulator)
+ reducer :: (entry :: term(), accumulator -> accumulator),
+ limited? :: boolean()
) :: {:ok, accumulator}
when accumulator: term()
- def stream_unfetched_uncles(initial, reducer) when is_function(reducer, 2) do
+ def stream_unfetched_uncles(initial, reducer, limited? \\ false) when is_function(reducer, 2) do
query =
from(bsdr in Block.SecondDegreeRelation,
where: is_nil(bsdr.uncle_fetched_at) and not is_nil(bsdr.index),
select: [:nephew_hash, :index]
)
- Repo.stream_reduce(query, initial, reducer)
+ query
+ |> add_fetcher_limit(limited?)
+ |> Repo.stream_reduce(initial, reducer)
end
@doc """
@@ -3541,11 +2596,12 @@ defmodule Explorer.Chain do
options
) do
paging_options
- |> fetch_transactions()
+ |> Transaction.fetch_transactions()
|> where([transaction], not is_nil(transaction.block_number) and not is_nil(transaction.index))
|> apply_filter_by_method_id_to_transactions(method_id_filter)
|> apply_filter_by_tx_type_to_transactions(type_filter)
|> join_associations(necessity_by_association)
+ |> Transaction.put_has_token_transfers_to_tx(old_ui?)
|> (&if(old_ui?, do: preload(&1, [{:token_transfers, [:token, :from_address, :to_address]}]), else: &1)).()
|> select_repo(options).all()
|> (&if(old_ui?,
@@ -3590,7 +2646,7 @@ defmodule Explorer.Chain do
type_filter = Keyword.get(options, :type)
Transaction
- |> page_pending_transaction(paging_options)
+ |> Transaction.page_pending_transaction(paging_options)
|> limit(^paging_options.page_size)
|> pending_transactions_query()
|> apply_filter_by_method_id_to_transactions(method_id_filter)
@@ -3599,11 +2655,6 @@ defmodule Explorer.Chain do
|> join_associations(necessity_by_association)
|> (&if(old_ui?, do: preload(&1, [{:token_transfers, [:token, :from_address, :to_address]}]), else: &1)).()
|> select_repo(options).all()
- |> (&if(old_ui?,
- do: &1,
- else:
- Enum.map(&1, fn tx -> preload_token_transfers(tx, @token_transfers_necessity_by_association, options) end)
- )).()
end
def pending_transactions_query(query) do
@@ -3745,7 +2796,6 @@ defmodule Explorer.Chain do
|> page_internal_transaction(paging_options)
|> limit(^paging_options.page_size)
|> order_by([internal_transaction], asc: internal_transaction.index)
- |> preload(:transaction)
|> select_repo(options).all()
end
@@ -3766,7 +2816,7 @@ defmodule Explorer.Chain do
|> page_internal_transaction(paging_options)
|> limit(^paging_options.page_size)
|> order_by([internal_transaction], asc: internal_transaction.index)
- |> preload(:transaction)
+ |> preload(:block)
|> select_repo(options).all()
end
@@ -3799,7 +2849,7 @@ defmodule Explorer.Chain do
query =
log_with_transactions
|> where([_, transaction], transaction.hash == ^transaction_hash)
- |> page_logs(paging_options)
+ |> page_transaction_logs(paging_options)
|> limit(^paging_options.page_size)
|> order_by([log], asc: log.index)
|> join_associations(necessity_by_association)
@@ -3821,7 +2871,7 @@ defmodule Explorer.Chain do
the `index` that are passed.
"""
- @spec transaction_to_token_transfers(Hash.Full.t(), [paging_options | necessity_by_association_option]) :: [
+ @spec transaction_to_token_transfers(Hash.Full.t(), [paging_options | necessity_by_association_option | api?()]) :: [
TokenTransfer.t()
]
def transaction_to_token_transfers(transaction_hash, options \\ []) when is_list(options) do
@@ -4001,12 +3051,7 @@ defmodule Explorer.Chain do
The `t:Explorer.Chain.Transaction.t/0` or `t:Explorer.Chain.InternalTransaction.t/0` `value` of the `transaction` in
`unit`.
"""
- @spec value(InternalTransaction.t(), :wei) :: Wei.wei()
- @spec value(InternalTransaction.t(), :gwei) :: Wei.gwei()
- @spec value(InternalTransaction.t(), :ether) :: Wei.ether()
- @spec value(Transaction.t(), :wei) :: Wei.wei()
- @spec value(Transaction.t(), :gwei) :: Wei.gwei()
- @spec value(Transaction.t(), :ether) :: Wei.ether()
+ @spec value(InternalTransaction.t() | Transaction.t(), :wei | :gwei | :ether) :: Wei.wei() | Wei.gwei() | Wei.ether()
def value(%type{value: value}, unit) when type in [InternalTransaction, Transaction] do
Wei.to(value, unit)
end
@@ -4152,442 +3197,16 @@ defmodule Explorer.Chain do
end
end
- @doc """
- Inserts a `t:SmartContract.t/0`.
-
- As part of inserting a new smart contract, an additional record is inserted for
- naming the address for reference.
- """
- @spec create_smart_contract(map()) :: {:ok, SmartContract.t()} | {:error, Ecto.Changeset.t()}
- def create_smart_contract(attrs \\ %{}, external_libraries \\ [], secondary_sources \\ []) do
- new_contract = %SmartContract{}
-
- attrs =
- attrs
- |> Helper.add_contract_code_md5()
-
- smart_contract_changeset =
- new_contract
- |> SmartContract.changeset(attrs)
- |> Changeset.put_change(:external_libraries, external_libraries)
-
- new_contract_additional_source = %SmartContractAdditionalSource{}
-
- smart_contract_additional_sources_changesets =
- if secondary_sources do
- secondary_sources
- |> Enum.map(fn changeset ->
- new_contract_additional_source
- |> SmartContractAdditionalSource.changeset(changeset)
- end)
- else
- []
- end
-
- address_hash = Changeset.get_field(smart_contract_changeset, :address_hash)
-
- # Enforce ShareLocks tables order (see docs: sharelocks.md)
- insert_contract_query =
- Multi.new()
- |> Multi.run(:set_address_verified, fn repo, _ -> set_address_verified(repo, address_hash) end)
- |> Multi.run(:clear_primary_address_names, fn repo, _ -> clear_primary_address_names(repo, address_hash) end)
- |> Multi.insert(:smart_contract, smart_contract_changeset)
-
- insert_contract_query_with_additional_sources =
- smart_contract_additional_sources_changesets
- |> Enum.with_index()
- |> Enum.reduce(insert_contract_query, fn {changeset, index}, multi ->
- Multi.insert(multi, "smart_contract_additional_source_#{Integer.to_string(index)}", changeset)
- end)
-
- insert_result =
- insert_contract_query_with_additional_sources
- |> Repo.transaction()
-
- create_address_name(Repo, Changeset.get_field(smart_contract_changeset, :name), address_hash)
-
- case insert_result do
- {:ok, %{smart_contract: smart_contract}} ->
- {:ok, smart_contract}
-
- {:error, :smart_contract, changeset, _} ->
- {:error, changeset}
-
- {:error, :set_address_verified, message, _} ->
- {:error, message}
- end
- end
-
- @doc """
- Updates a `t:SmartContract.t/0`.
-
- Has the similar logic as create_smart_contract/1.
- Used in cases when you need to update row in DB contains SmartContract, e.g. in case of changing
- status `partially verified` to `fully verified` (re-verify).
- """
- @spec update_smart_contract(map()) :: {:ok, SmartContract.t()} | {:error, Ecto.Changeset.t()}
- def update_smart_contract(attrs \\ %{}, external_libraries \\ [], secondary_sources \\ []) do
- address_hash = Map.get(attrs, :address_hash)
-
- query =
- from(
- smart_contract in SmartContract,
- where: smart_contract.address_hash == ^address_hash
- )
-
- query_sources =
- from(
- source in SmartContractAdditionalSource,
- where: source.address_hash == ^address_hash
- )
-
- _delete_sources = Repo.delete_all(query_sources)
-
- smart_contract = Repo.one(query)
-
- smart_contract_changeset =
- smart_contract
- |> SmartContract.changeset(attrs)
- |> Changeset.put_change(:external_libraries, external_libraries)
-
- new_contract_additional_source = %SmartContractAdditionalSource{}
-
- smart_contract_additional_sources_changesets =
- if secondary_sources do
- secondary_sources
- |> Enum.map(fn changeset ->
- new_contract_additional_source
- |> SmartContractAdditionalSource.changeset(changeset)
- end)
- else
- []
- end
-
- # Enforce ShareLocks tables order (see docs: sharelocks.md)
- insert_contract_query =
- Multi.new()
- |> Multi.update(:smart_contract, smart_contract_changeset)
-
- insert_contract_query_with_additional_sources =
- smart_contract_additional_sources_changesets
- |> Enum.with_index()
- |> Enum.reduce(insert_contract_query, fn {changeset, index}, multi ->
- Multi.insert(multi, "smart_contract_additional_source_#{Integer.to_string(index)}", changeset)
- end)
-
- insert_result =
- insert_contract_query_with_additional_sources
- |> Repo.transaction()
-
- case insert_result do
- {:ok, %{smart_contract: smart_contract}} ->
- {:ok, smart_contract}
-
- {:error, :smart_contract, changeset, _} ->
- {:error, changeset}
-
- {:error, :set_address_verified, message, _} ->
- {:error, message}
- end
- end
-
- defp set_address_verified(repo, address_hash) do
- query =
- from(
- address in Address,
- where: address.hash == ^address_hash
- )
-
- case repo.update_all(query, set: [verified: true]) do
- {1, _} -> {:ok, []}
- _ -> {:error, "There was an error annotating that the address has been verified."}
- end
- end
-
- defp set_address_decompiled(repo, address_hash) do
- query =
- from(
- address in Address,
- where: address.hash == ^address_hash
- )
-
- case repo.update_all(query, set: [decompiled: true]) do
- {1, _} -> {:ok, []}
- _ -> {:error, "There was an error annotating that the address has been decompiled."}
- end
- end
-
- defp clear_primary_address_names(repo, address_hash) do
- query =
- from(
- address_name in Address.Name,
- where: address_name.address_hash == ^address_hash,
- # Enforce Name ShareLocks order (see docs: sharelocks.md)
- order_by: [asc: :address_hash, asc: :name],
- lock: "FOR UPDATE"
- )
-
- repo.update_all(
- from(n in Address.Name, join: s in subquery(query), on: n.address_hash == s.address_hash and n.name == s.name),
- set: [primary: false]
- )
-
- {:ok, []}
- end
-
- defp create_address_name(repo, name, address_hash) do
- params = %{
- address_hash: address_hash,
- name: name,
- primary: true
- }
-
- %Address.Name{}
- |> Address.Name.changeset(params)
- |> repo.insert(on_conflict: :nothing, conflict_target: [:address_hash, :name])
- end
-
- def get_verified_twin_contract(%Explorer.Chain.Address{} = target_address, options \\ []) do
- case target_address do
- %{contract_code: %Chain.Data{bytes: contract_code_bytes}} ->
- target_address_hash = target_address.hash
-
- contract_code_md5 = Helper.contract_code_md5(contract_code_bytes)
-
- verified_contract_twin_query =
- from(
- smart_contract in SmartContract,
- where: smart_contract.contract_code_md5 == ^contract_code_md5,
- where: smart_contract.address_hash != ^target_address_hash,
- select: smart_contract,
- limit: 1
- )
-
- verified_contract_twin_query
- |> select_repo(options).one(timeout: 10_000)
-
- _ ->
- nil
- end
- end
-
- @doc """
- Finds metadata for verification of a contract from verified twins: contracts with the same bytecode
- which were verified previously, returns a single t:SmartContract.t/0
- """
- def get_address_verified_twin_contract(hash, options \\ [])
-
- def get_address_verified_twin_contract(hash, options) when is_binary(hash) do
- case string_to_address_hash(hash) do
- {:ok, address_hash} -> get_address_verified_twin_contract(address_hash, options)
- _ -> %{:verified_contract => nil, :additional_sources => nil}
- end
- end
-
- def get_address_verified_twin_contract(%Explorer.Chain.Hash{} = address_hash, options) do
- with target_address <- select_repo(options).get(Address, address_hash),
- false <- is_nil(target_address) do
- verified_contract_twin = get_verified_twin_contract(target_address, options)
-
- verified_contract_twin_additional_sources = get_contract_additional_sources(verified_contract_twin, options)
-
- %{
- :verified_contract => verified_contract_twin,
- :additional_sources => verified_contract_twin_additional_sources
- }
- else
- _ ->
- %{:verified_contract => nil, :additional_sources => nil}
- end
- end
-
- def get_minimal_proxy_template(address_hash, options \\ []) do
- minimal_proxy_template =
- case select_repo(options).get(Address, address_hash) do
- nil ->
- nil
-
- target_address ->
- contract_code = target_address.contract_code
-
- case contract_code do
- %Chain.Data{bytes: contract_code_bytes} ->
- contract_bytecode = Base.encode16(contract_code_bytes, case: :lower)
-
- get_minimal_proxy_from_template_code(contract_bytecode, options)
-
- _ ->
- nil
- end
- end
-
- minimal_proxy_template
- end
-
- defp get_minimal_proxy_from_template_code(contract_bytecode, options) do
- case contract_bytecode do
- "363d3d373d3d3d363d73" <> <> <> _ ->
- template_address = "0x" <> template_address
-
- query =
- from(
- smart_contract in SmartContract,
- where: smart_contract.address_hash == ^template_address,
- select: smart_contract
- )
-
- template =
- query
- |> select_repo(options).one(timeout: 10_000)
-
- template
-
- _ ->
- nil
- end
- end
-
- defp get_contract_additional_sources(verified_contract_twin, options) do
- if verified_contract_twin do
- verified_contract_twin_additional_sources_query =
- from(
- s in SmartContractAdditionalSource,
- where: s.address_hash == ^verified_contract_twin.address_hash
- )
-
- verified_contract_twin_additional_sources_query
- |> select_repo(options).all()
- else
- []
- end
- end
-
- @spec address_hash_to_smart_contract(Hash.Address.t(), [api?]) :: SmartContract.t() | nil
- def address_hash_to_smart_contract(address_hash, options \\ []) do
- query =
- from(
- smart_contract in SmartContract,
- where: smart_contract.address_hash == ^address_hash
- )
-
- current_smart_contract = select_repo(options).one(query)
-
- if current_smart_contract do
- current_smart_contract
- else
- address_verified_twin_contract =
- get_minimal_proxy_template(address_hash, options) ||
- get_address_verified_twin_contract(address_hash, options).verified_contract
-
- if address_verified_twin_contract do
- address_verified_twin_contract
- |> Map.put(:address_hash, address_hash)
- |> Map.put(:metadata_from_verified_twin, true)
- |> Map.put(:implementation_address_hash, nil)
- |> Map.put(:implementation_name, nil)
- |> Map.put(:implementation_fetched_at, nil)
- else
- current_smart_contract
- end
- end
- end
-
- @spec address_hash_to_smart_contract(Hash.Address.t()) :: SmartContract.t() | nil
- def address_hash_to_one_smart_contract(hash) do
- SmartContract
- |> where([sc], sc.address_hash == ^hash)
- |> Repo.one()
- end
-
- @spec address_hash_to_smart_contract_without_twin(Hash.Address.t(), [api?]) :: SmartContract.t() | nil
- def address_hash_to_smart_contract_without_twin(address_hash, options) do
- query =
- from(
- smart_contract in SmartContract,
- where: smart_contract.address_hash == ^address_hash
- )
-
- select_repo(options).one(query)
- end
-
- def smart_contract_fully_verified?(address_hash, options \\ [])
-
- def smart_contract_fully_verified?(address_hash_str, options) when is_binary(address_hash_str) do
- case string_to_address_hash(address_hash_str) do
- {:ok, address_hash} ->
- check_fully_verified(address_hash, options)
-
- _ ->
- false
- end
- end
-
- def smart_contract_fully_verified?(address_hash, options) do
- check_fully_verified(address_hash, options)
- end
-
- defp check_fully_verified(address_hash, options) do
- query =
- from(
- smart_contract in SmartContract,
- where: smart_contract.address_hash == ^address_hash
- )
-
- result = select_repo(options).one(query)
-
- if result, do: !result.partially_verified, else: false
- end
-
- def smart_contract_verified?(address_hash_str) when is_binary(address_hash_str) do
- case string_to_address_hash(address_hash_str) do
- {:ok, address_hash} ->
- check_verified(address_hash)
-
- _ ->
- false
- end
- end
-
- def smart_contract_verified?(address_hash) do
- check_verified(address_hash)
- end
-
- defp check_verified(address_hash) do
- query =
- from(
- smart_contract in SmartContract,
- where: smart_contract.address_hash == ^address_hash
- )
-
- if Repo.one(query), do: true, else: false
- end
-
- defp fetch_transactions(paging_options \\ nil, from_block \\ nil, to_block \\ nil, with_pending? \\ false) do
+ defp fetch_transactions_in_ascending_order_by_index(paging_options) do
Transaction
- |> order_for_transactions(with_pending?)
- |> where_block_number_in_period(from_block, to_block)
- |> handle_paging_options(paging_options)
- end
-
- defp order_for_transactions(query, true) do
- query
- |> order_by([transaction],
- desc: transaction.block_number,
- desc: transaction.index,
- desc: transaction.inserted_at,
- asc: transaction.hash
- )
- end
-
- defp order_for_transactions(query, _) do
- query
- |> order_by([transaction], desc: transaction.block_number, desc: transaction.index)
+ |> order_by([transaction], asc: transaction.index)
+ |> handle_block_paging_options(paging_options)
end
- defp fetch_transactions_in_ascending_order_by_index(paging_options) do
+ defp fetch_transactions_in_descending_order_by_block_and_index(paging_options) do
Transaction
|> order_by([transaction], desc: transaction.block_number, asc: transaction.index)
- |> handle_paging_options(paging_options)
+ |> handle_block_paging_options(paging_options)
end
defp for_parent_transaction(query, %Hash{byte_count: unquote(Hash.Full.byte_count())} = hash) do
@@ -4598,29 +3217,13 @@ defmodule Explorer.Chain do
)
end
- defp handle_paging_options(query, nil), do: query
-
- defp handle_paging_options(query, %PagingOptions{key: nil, page_size: nil}), do: query
-
- defp handle_paging_options(query, paging_options) do
- query
- |> page_transaction(paging_options)
- |> limit(^paging_options.page_size)
- end
-
- defp handle_verified_contracts_paging_options(query, nil), do: query
-
- defp handle_verified_contracts_paging_options(query, paging_options) do
- query
- |> page_verified_contracts(paging_options)
- |> limit(^paging_options.page_size)
- end
+ defp handle_block_paging_options(query, nil), do: query
- defp handle_token_transfer_paging_options(query, nil), do: query
+ defp handle_block_paging_options(query, %PagingOptions{key: nil, page_size: nil}), do: query
- defp handle_token_transfer_paging_options(query, paging_options) do
+ defp handle_block_paging_options(query, paging_options) do
query
- |> TokenTransfer.page_token_transfer(paging_options)
+ |> page_block_transactions(paging_options)
|> limit(^paging_options.page_size)
end
@@ -4639,7 +3242,7 @@ defmodule Explorer.Chain do
query
|> (&if(paging_options |> Map.get(:page_number, 1) |> process_page_number() == 1,
do: &1,
- else: page_transaction(&1, paging_options)
+ else: Transaction.page_transaction(&1, paging_options)
)).()
|> handle_page(paging_options)
end
@@ -4682,67 +3285,32 @@ defmodule Explorer.Chain do
:required ->
from(q in query,
inner_join: a in assoc(q, ^association),
+ as: ^association,
left_join: b in assoc(a, ^nested_preload),
+ as: ^nested_preload,
preload: [{^association, {a, [{^nested_preload, b}]}}]
)
end
end
- defp join_association(query, association, necessity) when is_atom(association) do
- case necessity do
- :optional ->
- preload(query, ^association)
-
- :required ->
- from(q in query, inner_join: a in assoc(q, ^association), preload: [{^association, a}])
- end
- end
-
defp join_association(query, association, necessity) do
case necessity do
:optional ->
preload(query, ^association)
:required ->
- from(q in query, inner_join: a in assoc(q, ^association), preload: [{^association, a}])
+ from(q in query, inner_join: a in assoc(q, ^association), as: ^association, preload: [{^association, a}])
end
end
- defp join_associations(query, necessity_by_association) when is_map(necessity_by_association) do
- Enum.reduce(necessity_by_association, query, fn {association, join}, acc_query ->
- join_association(acc_query, association, join)
- end)
- end
-
- defp page_addresses(query, %PagingOptions{key: nil}), do: query
-
- defp page_addresses(query, %PagingOptions{key: {coin_balance, hash}}) do
- from(address in query,
- where:
- (address.fetched_coin_balance == ^coin_balance and address.hash > ^hash) or
- address.fetched_coin_balance < ^coin_balance
- )
- end
-
- defp page_tokens(query, %PagingOptions{key: nil}), do: query
-
- defp page_tokens(query, %PagingOptions{key: {nil, holder_count, name}}) do
- from(token in query,
- where:
- is_nil(token.circulating_market_cap) and
- (token.holder_count < ^holder_count or (token.holder_count == ^holder_count and token.name > ^name))
- )
- end
-
- defp page_tokens(query, %PagingOptions{key: {circulating_market_cap, holder_count, name}}) do
- from(token in query,
- where:
- is_nil(token.circulating_market_cap) or
- (token.circulating_market_cap < ^circulating_market_cap or
- (token.circulating_market_cap == ^circulating_market_cap and token.holder_count < ^holder_count) or
- (token.circulating_market_cap == ^circulating_market_cap and token.holder_count == ^holder_count and
- token.name > ^name))
- )
+ @spec join_associations(atom() | Ecto.Query.t(), map) :: Ecto.Query.t()
+ @doc """
+ Function to preload entities associated with selected in provided query items
+ """
+ def join_associations(query, necessity_by_association) when is_map(necessity_by_association) do
+ Enum.reduce(necessity_by_association, query, fn {association, join}, acc_query ->
+ join_association(acc_query, association, join)
+ end)
end
defp page_blocks(query, %PagingOptions{key: nil}), do: query
@@ -4805,75 +3373,32 @@ defmodule Explorer.Chain do
where(query, [log], log.index > ^index)
end
- defp page_pending_transaction(query, %PagingOptions{key: nil}), do: query
-
- defp page_pending_transaction(query, %PagingOptions{key: {inserted_at, hash}}) do
+ defp page_logs(query, %PagingOptions{key: {block_number, log_index}}) do
where(
query,
- [transaction],
- (is_nil(transaction.block_number) and
- (transaction.inserted_at < ^inserted_at or
- (transaction.inserted_at == ^inserted_at and transaction.hash > ^hash))) or
- not is_nil(transaction.block_number)
+ [log],
+ log.block_number < ^block_number or (log.block_number == ^block_number and log.index < ^log_index)
)
end
- defp page_transaction(query, %PagingOptions{key: nil}), do: query
-
- defp page_transaction(query, %PagingOptions{is_pending_tx: true} = options),
- do: page_pending_transaction(query, options)
-
- defp page_transaction(query, %PagingOptions{key: {block_number, index}, is_index_in_asc_order: true}) do
- where(
- query,
- [transaction],
- transaction.block_number < ^block_number or
- (transaction.block_number == ^block_number and transaction.index > ^index)
- )
- end
+ defp page_transaction_logs(query, %PagingOptions{key: nil}), do: query
- defp page_transaction(query, %PagingOptions{key: {block_number, index}}) do
- where(
- query,
- [transaction],
- transaction.block_number < ^block_number or
- (transaction.block_number == ^block_number and transaction.index < ^index)
- )
+ defp page_transaction_logs(query, %PagingOptions{key: {index}}) do
+ where(query, [log], log.index > ^index)
end
- defp page_transaction(query, %PagingOptions{key: {index}}) do
- where(query, [transaction], transaction.index < ^index)
+ defp page_transaction_logs(query, %PagingOptions{key: {_block_number, index}}) do
+ where(query, [log], log.index > ^index)
end
- defp page_search_results(query, %PagingOptions{key: nil}), do: query
+ defp page_block_transactions(query, %PagingOptions{key: nil}), do: query
- defp page_search_results(query, %PagingOptions{
- key: {_address_hash, _tx_hash, _block_hash, holder_count, name, inserted_at, item_type}
- })
- when holder_count in [nil, ""] do
- where(
- query,
- [item],
- (item.name > ^name and item.type == ^item_type) or
- (item.name == ^name and item.inserted_at < ^inserted_at and
- item.type == ^item_type) or
- item.type != ^item_type
- )
+ defp page_block_transactions(query, %PagingOptions{key: {_block_number, index}, is_index_in_asc_order: true}) do
+ where(query, [transaction], transaction.index > ^index)
end
- # credo:disable-for-next-line
- defp page_search_results(query, %PagingOptions{
- key: {_address_hash, _tx_hash, _block_hash, holder_count, name, inserted_at, item_type}
- }) do
- where(
- query,
- [item],
- (item.holder_count < ^holder_count and item.type == ^item_type) or
- (item.holder_count == ^holder_count and item.name > ^name and item.type == ^item_type) or
- (item.holder_count == ^holder_count and item.name == ^name and item.inserted_at < ^inserted_at and
- item.type == ^item_type) or
- item.type != ^item_type
- )
+ defp page_block_transactions(query, %PagingOptions{key: {_block_number, index}}) do
+ where(query, [transaction], transaction.index < ^index)
end
def page_token_balances(query, %PagingOptions{key: nil}), do: query
@@ -4928,12 +3453,6 @@ defmodule Explorer.Chain do
)
end
- defp page_verified_contracts(query, %PagingOptions{key: nil}), do: query
-
- defp page_verified_contracts(query, %PagingOptions{key: {id}}) do
- where(query, [contract], contract.id < ^id)
- end
-
@doc """
Ensures the following conditions are true:
@@ -4963,7 +3482,7 @@ defmodule Explorer.Chain do
end
@doc """
- The current total number of coins minted minus verifiably burned coins.
+ The current total number of coins minted minus verifiably burnt coins.
"""
@spec total_supply :: non_neg_integer() | nil
def total_supply do
@@ -4992,10 +3511,12 @@ defmodule Explorer.Chain do
"""
@spec stream_uncataloged_token_contract_address_hashes(
initial :: accumulator,
- reducer :: (entry :: Hash.Address.t(), accumulator -> accumulator)
+ reducer :: (entry :: Hash.Address.t(), accumulator -> accumulator),
+ limited? :: boolean()
) :: {:ok, accumulator}
when accumulator: term()
- def stream_uncataloged_token_contract_address_hashes(initial, reducer) when is_function(reducer, 2) do
+ def stream_uncataloged_token_contract_address_hashes(initial, reducer, limited? \\ false)
+ when is_function(reducer, 2) do
query =
from(
token in Token,
@@ -5003,62 +3524,41 @@ defmodule Explorer.Chain do
select: token.contract_address_hash
)
- Repo.stream_reduce(query, initial, reducer)
+ query
+ |> add_fetcher_limit(limited?)
+ |> Repo.stream_reduce(initial, reducer)
end
- @spec stream_unfetched_token_instances(
+ @doc """
+ Finds all token instances where metadata never tried to fetch
+ """
+ @spec stream_token_instances_with_unfetched_metadata(
initial :: accumulator,
reducer :: (entry :: map(), accumulator -> accumulator)
) :: {:ok, accumulator}
when accumulator: term()
- def stream_unfetched_token_instances(initial, reducer) when is_function(reducer, 2) do
- nft_tokens =
- from(
- token in Token,
- where: token.type == ^"ERC-721" or token.type == ^"ERC-1155",
- select: token.contract_address_hash
- )
-
- token_ids_query =
- from(
- token_transfer in TokenTransfer,
- select: %{
- token_contract_address_hash: token_transfer.token_contract_address_hash,
- token_id: fragment("unnest(?)", token_transfer.token_ids)
- }
- )
-
- query =
- from(
- transfer in subquery(token_ids_query),
- inner_join: token in subquery(nft_tokens),
- on: token.contract_address_hash == transfer.token_contract_address_hash,
- left_join: instance in Instance,
- on:
- transfer.token_contract_address_hash == instance.token_contract_address_hash and
- transfer.token_id == instance.token_id,
- where: is_nil(instance.token_id),
- select: %{
- contract_address_hash: transfer.token_contract_address_hash,
- token_id: transfer.token_id
- }
- )
-
- distinct_query =
- from(
- q in subquery(query),
- distinct: [q.contract_address_hash, q.token_id]
- )
-
- Repo.stream_reduce(distinct_query, initial, reducer)
+ def stream_token_instances_with_unfetched_metadata(initial, reducer) when is_function(reducer, 2) do
+ Instance
+ |> where([instance], is_nil(instance.error) and is_nil(instance.metadata))
+ |> select([instance], %{
+ contract_address_hash: instance.token_contract_address_hash,
+ token_id: instance.token_id
+ })
+ |> Repo.stream_reduce(initial, reducer)
end
@spec stream_token_instances_with_error(
initial :: accumulator,
- reducer :: (entry :: map(), accumulator -> accumulator)
+ reducer :: (entry :: map(), accumulator -> accumulator),
+ limited? :: boolean()
) :: {:ok, accumulator}
when accumulator: term()
- def stream_token_instances_with_error(initial, reducer) when is_function(reducer, 2) do
+ def stream_token_instances_with_error(initial, reducer, limited? \\ false) when is_function(reducer, 2) do
+ # likely to get valid metadata
+ high_priority = ["request error: 429", ":checkout_timeout", ":econnrefused", ":timeout"]
+ # almost impossible to get valid metadata
+ negative_priority = ["VM execution error", "no uri", "invalid json"]
+
Instance
|> where([instance], not is_nil(instance.error))
|> select([instance], %{
@@ -5066,6 +3566,8 @@ defmodule Explorer.Chain do
token_id: instance.token_id,
updated_at: instance.updated_at
})
+ |> order_by([instance], desc: instance.error in ^high_priority, asc: instance.error in ^negative_priority)
+ |> add_fetcher_limit(limited?)
|> Repo.stream_reduce(initial, reducer)
end
@@ -5074,40 +3576,20 @@ defmodule Explorer.Chain do
"""
@spec stream_cataloged_token_contract_address_hashes(
initial :: accumulator,
- reducer :: (entry :: Hash.Address.t(), accumulator -> accumulator)
+ reducer :: (entry :: Hash.Address.t(), accumulator -> accumulator),
+ some_time_ago_updated :: integer(),
+ limited? :: boolean()
) :: {:ok, accumulator}
when accumulator: term()
- def stream_cataloged_token_contract_address_hashes(initial, reducer, some_time_ago_updated \\ 2880)
+ def stream_cataloged_token_contract_address_hashes(initial, reducer, some_time_ago_updated \\ 2880, limited? \\ false)
when is_function(reducer, 2) do
some_time_ago_updated
|> Token.cataloged_tokens()
+ |> add_fetcher_limit(limited?)
|> order_by(asc: :updated_at)
|> Repo.stream_reduce(initial, reducer)
end
- @doc """
- Returns a list of block numbers token transfer `t:Log.t/0`s that don't have an
- associated `t:TokenTransfer.t/0` record.
- """
- def uncataloged_token_transfer_block_numbers do
- query =
- from(l in Log,
- as: :log,
- where: l.first_topic == unquote(TokenTransfer.constant()),
- where:
- not exists(
- from(tf in TokenTransfer,
- where: tf.transaction_hash == parent_as(:log).transaction_hash,
- where: tf.log_index == parent_as(:log).index
- )
- ),
- select: l.block_number,
- distinct: l.block_number
- )
-
- Repo.stream_reduce(query, [], &[&1 | &2])
- end
-
def decode_contract_address_hash_response(resp) do
case resp do
"0x000000000000000000000000" <> address ->
@@ -5138,12 +3620,9 @@ defmodule Explorer.Chain do
`:required`, and the `t:Token.t/0` has no associated record for that association,
then the `t:Token.t/0` will not be included in the list.
"""
- @spec token_from_address_hash(Hash.Address.t(), [necessity_by_association_option | api?]) ::
+ @spec token_from_address_hash(Hash.Address.t() | String.t(), [necessity_by_association_option | api?]) ::
{:ok, Token.t()} | {:error, :not_found}
- def token_from_address_hash(
- %Hash{byte_count: unquote(Hash.Address.byte_count())} = hash,
- options \\ []
- ) do
+ def token_from_address_hash(hash, options \\ []) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
query =
@@ -5163,12 +3642,21 @@ defmodule Explorer.Chain do
%Token{} = token ->
{:ok, token}
-
- [%Token{} = token, nil] ->
- {:ok, token}
end
end
+ @spec token_from_address_hash_exists?(Hash.Address.t(), [api?]) :: boolean()
+ def token_from_address_hash_exists?(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash, options) do
+ query =
+ from(
+ t in Token,
+ where: t.contract_address_hash == ^hash,
+ select: t
+ )
+
+ select_repo(options).exists?(query)
+ end
+
@spec fetch_token_transfers_from_token_hash(Hash.t(), [paging_options]) :: []
def fetch_token_transfers_from_token_hash(token_address_hash, options \\ []) do
TokenTransfer.fetch_token_transfers_from_token_hash(token_address_hash, options)
@@ -5196,13 +3684,6 @@ defmodule Explorer.Chain do
Repo.exists?(query)
end
- @spec address_has_rewards?(Address.t()) :: boolean()
- def address_has_rewards?(address_hash) do
- query = from(r in Reward, where: r.address_hash == ^address_hash)
-
- Repo.exists?(query)
- end
-
@spec address_tokens_with_balance(Hash.Address.t(), [any()]) :: []
def address_tokens_with_balance(address_hash, paging_options \\ []) do
address_hash
@@ -5232,7 +3713,7 @@ defmodule Explorer.Chain do
)
# Enforce Transaction ShareLocks order (see docs: sharelocks.md)
|> order_by(asc: :hash)
- |> lock("FOR UPDATE")
+ |> lock("FOR NO KEY UPDATE")
hashes = Enum.map(transactions, & &1.hash)
@@ -5277,7 +3758,7 @@ defmodule Explorer.Chain do
end)
# Enforce Transaction ShareLocks order (see docs: sharelocks.md)
|> order_by(asc: :hash)
- |> lock("FOR UPDATE")
+ |> lock("FOR NO KEY UPDATE")
Repo.update_all(
from(t in Transaction, join: s in subquery(query), on: t.hash == s.hash),
@@ -5287,16 +3768,38 @@ defmodule Explorer.Chain do
end
end
+ @doc """
+ Expects map of change params. Inserts using on_conflict: `token_instance_metadata_on_conflict/0`
+ !!! Supposed to be used ONLY for import of `metadata` or `error`.
+ """
@spec upsert_token_instance(map()) :: {:ok, Instance.t()} | {:error, Ecto.Changeset.t()}
def upsert_token_instance(params) do
changeset = Instance.changeset(%Instance{}, params)
Repo.insert(changeset,
- on_conflict: :replace_all,
+ on_conflict: token_instance_metadata_on_conflict(),
conflict_target: [:token_id, :token_contract_address_hash]
)
end
+ defp token_instance_metadata_on_conflict do
+ from(
+ token_instance in Instance,
+ update: [
+ set: [
+ metadata: fragment("EXCLUDED.metadata"),
+ error: fragment("EXCLUDED.error"),
+ owner_updated_at_block: token_instance.owner_updated_at_block,
+ owner_updated_at_log_index: token_instance.owner_updated_at_log_index,
+ owner_address_hash: token_instance.owner_address_hash,
+ inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", token_instance.inserted_at),
+ updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", token_instance.updated_at)
+ ]
+ ],
+ where: is_nil(token_instance.metadata)
+ )
+ end
+
@doc """
Update a new `t:Token.t/0` record.
@@ -5347,6 +3850,13 @@ defmodule Explorer.Chain do
end
end
+ @spec fetch_last_token_balances_include_unfetched(Hash.Address.t(), [api?]) :: []
+ def fetch_last_token_balances_include_unfetched(address_hash, options \\ []) do
+ address_hash
+ |> CurrentTokenBalance.last_token_balances_include_unfetched()
+ |> select_repo(options).all()
+ end
+
@spec fetch_last_token_balances(Hash.Address.t(), [api?]) :: []
def fetch_last_token_balances(address_hash, options \\ []) do
address_hash
@@ -5383,16 +3893,20 @@ defmodule Explorer.Chain do
select_repo(options).exists?(query)
end
- defp fetch_coin_balances(address_hash, paging_options) do
- address = Repo.get_by(Address, hash: address_hash)
+ @spec token_instance_with_unfetched_metadata?(non_neg_integer, Hash.Address.t(), [api?]) :: boolean
+ def token_instance_with_unfetched_metadata?(token_id, token_contract_address, options \\ []) do
+ Instance
+ |> where([instance], is_nil(instance.error) and is_nil(instance.metadata))
+ |> where(
+ [instance],
+ instance.token_id == ^token_id and instance.token_contract_address_hash == ^token_contract_address
+ )
+ |> select_repo(options).exists?()
+ end
- if contract?(address) do
- address_hash
- |> CoinBalance.fetch_coin_balances(paging_options)
- else
- address_hash
- |> CoinBalance.fetch_coin_balances_with_txs(paging_options)
- end
+ defp fetch_coin_balances(address, paging_options) do
+ address.hash
+ |> CoinBalance.fetch_coin_balances(paging_options)
end
@spec fetch_last_token_balance(Hash.Address.t(), Hash.Address.t()) :: Decimal.t()
@@ -5415,15 +3929,16 @@ defmodule Explorer.Chain do
end
end
- @spec address_to_coin_balances(Hash.Address.t(), [paging_options | api?]) :: []
- def address_to_coin_balances(address_hash, options) do
+ @spec address_to_coin_balances(Address.t(), [paging_options | api?]) :: []
+ def address_to_coin_balances(address, options) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
balances_raw =
- address_hash
+ address
|> fetch_coin_balances(paging_options)
|> page_coin_balances(paging_options)
|> select_repo(options).all()
+ |> preload_transactions(options)
if Enum.empty?(balances_raw) do
balances_raw
@@ -5473,6 +3988,44 @@ defmodule Explorer.Chain do
end
end
+ # Here we fetch from DB one tx per one coin balance. It's much more faster than LEFT OUTER JOIN which was before.
+ defp preload_transactions(balances, options) do
+ tasks =
+ Enum.map(balances, fn balance ->
+ Task.async(fn ->
+ Transaction
+ |> where(
+ [tx],
+ tx.block_number == ^balance.block_number and (tx.value > ^0 or (tx.gas_price > ^0 and tx.gas_used > ^0)) and
+ (tx.to_address_hash == ^balance.address_hash or tx.from_address_hash == ^balance.address_hash)
+ )
+ |> select([tx], tx.hash)
+ |> limit(1)
+ |> select_repo(options).one()
+ end)
+ end)
+
+ tasks
+ |> Task.yield_many(120_000)
+ |> Enum.zip(balances)
+ |> Enum.map(fn {{task, res}, balance} ->
+ case res do
+ {:ok, hash} ->
+ put_tx_hash(hash, balance)
+
+ {:exit, _reason} ->
+ balance
+
+ nil ->
+ Task.shutdown(task, :brutal_kill)
+ balance
+ end
+ end)
+ end
+
+ defp put_tx_hash(hash, coin_balance),
+ do: if(hash, do: %CoinBalance{coin_balance | transaction_hash: hash}, else: coin_balance)
+
defp add_block_timestamp_to_balances(
balances_raw_filtered,
min_block_number,
@@ -5507,16 +4060,16 @@ defmodule Explorer.Chain do
%{balance | block_timestamp: formatted_date}
end
- def get_token_balance(address_hash, token_contract_address_hash, block_number) do
- query = TokenBalance.fetch_token_balance(address_hash, token_contract_address_hash, block_number)
+ def get_token_balance(address_hash, token_contract_address_hash, block_number, token_id \\ nil, options \\ []) do
+ query = TokenBalance.fetch_token_balance(address_hash, token_contract_address_hash, block_number, token_id)
- Repo.one(query)
+ select_repo(options).one(query)
end
- def get_coin_balance(address_hash, block_number) do
+ def get_coin_balance(address_hash, block_number, options \\ []) do
query = CoinBalance.fetch_coin_balance(address_hash, block_number)
- Repo.one(query)
+ select_repo(options).one(query)
end
@spec address_to_balances_by_day(Hash.Address.t(), [api?]) :: [balance_by_day]
@@ -5598,33 +4151,46 @@ defmodule Explorer.Chain do
def count_token_holders_from_token_hash(contract_address_hash) do
query =
from(ctb in CurrentTokenBalance.token_holders_query_for_count(contract_address_hash),
- select: fragment("COUNT(DISTINCT(address_hash))")
+ select: fragment("COUNT(DISTINCT(?))", ctb.address_hash)
)
Repo.one!(query, timeout: :infinity)
end
- @spec address_to_unique_tokens(Hash.Address.t(), [paging_options | api?]) :: [Instance.t()]
- def address_to_unique_tokens(contract_address_hash, options \\ []) do
+ @spec address_to_unique_tokens(Hash.Address.t(), Token.t(), [paging_options | api?]) :: [Instance.t()]
+ def address_to_unique_tokens(contract_address_hash, token, options \\ []) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
contract_address_hash
|> Instance.address_to_unique_token_instances()
|> Instance.page_token_instance(paging_options)
|> limit(^paging_options.page_size)
+ |> preload([_], [:owner])
|> select_repo(options).all()
- |> Enum.map(&put_owner_to_token_instance(&1, options))
+ |> Enum.map(&put_owner_to_token_instance(&1, token, options))
+ end
+
+ def put_owner_to_token_instance(token_instance, token, options \\ [])
+
+ def put_owner_to_token_instance(%Instance{is_unique: nil} = token_instance, token, options) do
+ put_owner_to_token_instance(Instance.put_is_unique(token_instance, token, options), token, options)
end
- def put_owner_to_token_instance(%Instance{} = token_instance, options \\ []) do
- owner =
+ def put_owner_to_token_instance(
+ %Instance{owner: nil, is_unique: true} = token_instance,
+ %Token{type: "ERC-1155"},
+ options
+ ) do
+ owner_address_hash =
token_instance
|> Instance.owner_query()
|> select_repo(options).one()
- %{token_instance | owner: owner}
+ %{token_instance | owner: select_repo(options).get_by(Address, hash: owner_address_hash)}
end
+ def put_owner_to_token_instance(%Instance{} = token_instance, _token, _options), do: token_instance
+
@spec data() :: Dataloader.Ecto.t()
def data, do: DataloaderEcto.new(Repo)
@@ -5928,36 +4494,6 @@ defmodule Explorer.Chain do
Repo.exists?(query)
end
- @doc """
- Checks if it exists a verified `t:Explorer.Chain.SmartContract.t/0` for the
- `t:Explorer.Chain.Address.t/0` with the provided `hash`.
-
- Returns `:ok` if found and `:not_found` otherwise.
- """
- @spec check_verified_smart_contract_exists(Hash.Address.t()) :: :ok | :not_found
- def check_verified_smart_contract_exists(address_hash) do
- address_hash
- |> verified_smart_contract_exists?()
- |> boolean_to_check_result()
- end
-
- @doc """
- Checks if it exists a verified `t:Explorer.Chain.SmartContract.t/0` for the
- `t:Explorer.Chain.Address.t/0` with the provided `hash`.
-
- Returns `true` if found and `false` otherwise.
- """
- @spec verified_smart_contract_exists?(Hash.Address.t()) :: boolean()
- def verified_smart_contract_exists?(address_hash) do
- query =
- from(
- smart_contract in SmartContract,
- where: smart_contract.address_hash == ^address_hash
- )
-
- Repo.exists?(query)
- end
-
@doc """
Checks if a `t:Explorer.Chain.Transaction.t/0` with the given `hash` exists.
@@ -6118,9 +4654,9 @@ defmodule Explorer.Chain do
Repo.exists?(query)
end
- defp boolean_to_check_result(true), do: :ok
+ def boolean_to_check_result(true), do: :ok
- defp boolean_to_check_result(false), do: :not_found
+ def boolean_to_check_result(false), do: :not_found
@doc """
Fetches the first trace from the Nethermind trace URL.
@@ -6138,88 +4674,6 @@ defmodule Explorer.Chain do
end
end
- def combine_proxy_implementation_abi(smart_contract, options \\ [])
-
- def combine_proxy_implementation_abi(%SmartContract{abi: abi} = smart_contract, options) when not is_nil(abi) do
- implementation_abi = get_implementation_abi_from_proxy(smart_contract, options)
-
- if Enum.empty?(implementation_abi), do: abi, else: implementation_abi ++ abi
- end
-
- def combine_proxy_implementation_abi(_, _) do
- []
- end
-
- def gnosis_safe_contract?(abi) when not is_nil(abi) do
- implementation_method_abi =
- abi
- |> Enum.find(fn method ->
- master_copy_pattern?(method)
- end)
-
- if implementation_method_abi, do: true, else: false
- end
-
- def gnosis_safe_contract?(abi) when is_nil(abi), do: false
-
- def master_copy_pattern?(method) do
- Map.get(method, "type") == "constructor" &&
- method
- |> Enum.find(fn item ->
- case item do
- {"inputs", inputs} ->
- master_copy_input?(inputs)
-
- _ ->
- false
- end
- end)
- end
-
- defp master_copy_input?(inputs) do
- inputs
- |> Enum.find(fn input ->
- Map.get(input, "name") == "_masterCopy"
- end)
- end
-
- def get_implementation_abi(implementation_address_hash_string, options \\ [])
-
- def get_implementation_abi(implementation_address_hash_string, options)
- when not is_nil(implementation_address_hash_string) do
- case Chain.string_to_address_hash(implementation_address_hash_string) do
- {:ok, implementation_address_hash} ->
- implementation_smart_contract =
- implementation_address_hash
- |> address_hash_to_smart_contract(options)
-
- if implementation_smart_contract do
- implementation_smart_contract
- |> Map.get(:abi)
- else
- []
- end
-
- _ ->
- []
- end
- end
-
- def get_implementation_abi(implementation_address_hash_string, _) when is_nil(implementation_address_hash_string) do
- []
- end
-
- def get_implementation_abi_from_proxy(
- %SmartContract{address_hash: proxy_address_hash, abi: abi} = smart_contract,
- options
- )
- when not is_nil(proxy_address_hash) and not is_nil(abi) do
- {implementation_address_hash_string, _name} = SmartContract.get_implementation_address_hash(smart_contract, options)
- get_implementation_abi(implementation_address_hash_string)
- end
-
- def get_implementation_abi_from_proxy(_, _), do: []
-
defp format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments) do
{:ok, to_address_hash} =
if Map.has_key?(first_trace, :to_address_hash) do
@@ -6316,7 +4770,7 @@ defmodule Explorer.Chain do
if transaction_index == 0 do
0
else
- filtered_block_numbers = EthereumJSONRPC.block_numbers_in_range([block_number])
+ filtered_block_numbers = RangesHelper.filter_traceable_block_numbers([block_number])
{:ok, traces} = fetch_block_internal_transactions(filtered_block_numbers, json_rpc_named_arguments)
sorted_traces =
@@ -6337,8 +4791,8 @@ defmodule Explorer.Chain do
defp find_block_timestamp(number, options) do
Block
- |> where([b], b.number == ^number)
- |> select([b], b.timestamp)
+ |> where([block], block.number == ^number)
+ |> select([block], block.timestamp)
|> limit(1)
|> select_repo(options).one()
end
@@ -6346,7 +4800,7 @@ defmodule Explorer.Chain do
@spec get_token_transfer_type(TokenTransfer.t()) ::
:token_burning | :token_minting | :token_spawning | :token_transfer
def get_token_transfer_type(transfer) do
- {:ok, burn_address_hash} = Chain.string_to_address_hash(@burn_address_hash_str)
+ {:ok, burn_address_hash} = Chain.string_to_address_hash(SmartContract.burn_address_hash_string())
cond do
transfer.to_address_hash == burn_address_hash && transfer.from_address_hash !== burn_address_hash ->
@@ -6390,10 +4844,12 @@ defmodule Explorer.Chain do
end
end
- defp from_block(options) do
+ @spec from_block(keyword) :: any
+ def from_block(options) do
Keyword.get(options, :from_block) || nil
end
+ @spec to_block(keyword) :: any
def to_block(options) do
Keyword.get(options, :to_block) || nil
end
@@ -6564,53 +5020,9 @@ defmodule Explorer.Chain do
end
def filter_token_creation_dynamic(dynamic) do
- dynamic([tx, created_token: created_token], ^dynamic or (is_nil(tx.to_address_hash) and not is_nil(created_token)))
- end
-
- @spec verified_contracts([
- paging_options
- | necessity_by_association_option
- | {:filter, :solidity | :vyper}
- | {:search, String.t() | {:api?, true | false}}
- ]) :: [SmartContract.t()]
- def verified_contracts(options \\ []) do
- paging_options = Keyword.get(options, :paging_options, @default_paging_options)
- necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
- filter = Keyword.get(options, :filter, nil)
- search_string = Keyword.get(options, :search, nil)
-
- query = from(contract in SmartContract, select: contract, order_by: [desc: :id])
-
- query
- |> filter_contracts(filter)
- |> search_contracts(search_string)
- |> handle_verified_contracts_paging_options(paging_options)
- |> join_associations(necessity_by_association)
- |> select_repo(options).all()
- end
-
- defp search_contracts(basic_query, nil), do: basic_query
-
- defp search_contracts(basic_query, search_string) do
- from(contract in basic_query,
- where:
- ilike(contract.name, ^"%#{search_string}%") or
- ilike(fragment("'0x' || encode(?, 'hex')", contract.address_hash), ^"%#{search_string}%")
- )
- end
-
- defp filter_contracts(basic_query, :solidity) do
- basic_query
- |> where(is_vyper_contract: ^false)
- end
-
- defp filter_contracts(basic_query, :vyper) do
- basic_query
- |> where(is_vyper_contract: ^true)
+ dynamic([tx, created_token: created_token], ^dynamic or not is_nil(created_token))
end
- defp filter_contracts(basic_query, _), do: basic_query
-
def count_verified_contracts do
Repo.aggregate(SmartContract, :count, timeout: :infinity)
end
@@ -6666,55 +5078,6 @@ defmodule Explorer.Chain do
NewContractsCounter.fetch(options)
end
- def address_counters(address, options \\ []) do
- validation_count_task =
- Task.async(fn ->
- address_to_validation_count(address.hash, options)
- end)
-
- Task.start_link(fn ->
- transaction_count(address)
- end)
-
- Task.start_link(fn ->
- token_transfers_count(address)
- end)
-
- Task.start_link(fn ->
- gas_usage_count(address)
- end)
-
- [
- validation_count_task
- ]
- |> Task.yield_many(:infinity)
- |> Enum.map(fn {_task, res} ->
- case res do
- {:ok, result} ->
- result
-
- {:exit, reason} ->
- raise "Query fetching address counters terminated: #{inspect(reason)}"
-
- nil ->
- raise "Query fetching address counters timed out."
- end
- end)
- |> List.to_tuple()
- end
-
- def transaction_count(address) do
- AddressTransactionsCounter.fetch(address)
- end
-
- def token_transfers_count(address) do
- AddressTokenTransfersCounter.fetch(address)
- end
-
- def gas_usage_count(address) do
- AddressTransactionsGasUsageCounter.fetch(address)
- end
-
def fetch_token_counters(address_hash, timeout) do
total_token_transfers_task =
Task.async(fn ->
@@ -6868,7 +5231,7 @@ defmodule Explorer.Chain do
end
def sum_withdrawals do
- Repo.aggregate(Withdrawal, :max, :index, timeout: :infinity)
+ Repo.aggregate(Withdrawal, :sum, :amount, timeout: :infinity)
end
def upsert_count_withdrawals(index) do
@@ -6885,4 +5248,33 @@ defmodule Explorer.Chain do
def count_withdrawals_from_cache(options \\ []) do
"withdrawals_count" |> get_last_fetched_counter(options) |> Decimal.add(1)
end
+
+ def add_fetcher_limit(query, false), do: query
+
+ def add_fetcher_limit(query, true) do
+ fetcher_limit = Application.get_env(:indexer, :fetcher_init_limit)
+
+ limit(query, ^fetcher_limit)
+ end
+
+ defp add_token_balances_fetcher_limit(query, false), do: query
+
+ defp add_token_balances_fetcher_limit(query, true) do
+ token_balances_fetcher_limit = Application.get_env(:indexer, :token_balances_fetcher_init_limit)
+
+ limit(query, ^token_balances_fetcher_limit)
+ end
+
+ defp add_coin_balances_fetcher_limit(query, false), do: query
+
+ defp add_coin_balances_fetcher_limit(query, true) do
+ coin_balances_fetcher_limit = Application.get_env(:indexer, :coin_balances_fetcher_init_limit)
+
+ limit(query, ^coin_balances_fetcher_limit)
+ end
+
+ @spec default_paging_options() :: map()
+ def default_paging_options do
+ @default_paging_options
+ end
end
diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex
index 44b317a42b7f..41bac5f23cf4 100644
--- a/apps/explorer/lib/explorer/chain/address.ex
+++ b/apps/explorer/lib/explorer/chain/address.ex
@@ -7,7 +7,9 @@ defmodule Explorer.Chain.Address do
use Explorer.Schema
+ alias Ecto.Association.NotLoaded
alias Ecto.Changeset
+ alias Explorer.{Chain, PagingOptions}
alias Explorer.Chain.{
Address,
@@ -24,7 +26,7 @@ defmodule Explorer.Chain.Address do
Withdrawal
}
- alias Explorer.Chain.Cache.NetVersion
+ alias Explorer.Chain.Cache.{Accounts, NetVersion}
@optional_attrs ~w(contract_code fetched_coin_balance fetched_coin_balance_block_number nonce decompiled verified gas_used transactions_count token_transfers_count)a
@required_attrs ~w(hash)a
@@ -45,7 +47,8 @@ defmodule Explorer.Chain.Address do
contract has been verified
* `names` - names known for the address
* `inserted_at` - when this address was inserted
- * `updated_at` when this address was last updated
+ * `updated_at` - when this address was last updated
+ * `ens_domain_name` - virtual field for ENS domain name passing
`fetched_coin_balance` and `fetched_coin_balance_block_number` may be updated when a new coin_balance row is fetched.
They may also be updated when the balance is fetched via the on demand fetcher.
@@ -62,7 +65,8 @@ defmodule Explorer.Chain.Address do
nonce: non_neg_integer() | nil,
transactions_count: non_neg_integer() | nil,
token_transfers_count: non_neg_integer() | nil,
- gas_used: non_neg_integer() | nil
+ gas_used: non_neg_integer() | nil,
+ ens_domain_name: String.t() | nil
}
@derive {Poison.Encoder,
@@ -102,6 +106,7 @@ defmodule Explorer.Chain.Address do
field(:transactions_count, :integer)
field(:token_transfers_count, :integer)
field(:gas_used, :integer)
+ field(:ens_domain_name, :string, virtual: true)
has_one(:smart_contract, SmartContract)
has_one(:token, Token, foreign_key: :contract_address_hash)
@@ -150,10 +155,17 @@ defmodule Explorer.Chain.Address do
def checksum(address_or_hash, iodata? \\ false)
+ def checksum(nil, _iodata?), do: ""
+
def checksum(%__MODULE__{hash: hash}, iodata?) do
checksum(hash, iodata?)
end
+ def checksum(hash_string, iodata?) when is_binary(hash_string) do
+ {:ok, hash} = Chain.string_to_address_hash(hash_string)
+ checksum(hash, iodata?)
+ end
+
def checksum(hash, iodata?) do
checksum_formatted =
case Application.get_env(:explorer, :checksum_function) || :eth do
@@ -250,6 +262,16 @@ defmodule Explorer.Chain.Address do
end)
end
+ @doc """
+ Preloads provided contracts associations if address has contract_code which is not nil
+ """
+ @spec maybe_preload_smart_contract_associations(Address.t(), list, list) :: Address.t()
+ def maybe_preload_smart_contract_associations(%Address{contract_code: nil} = address, _associations, _options),
+ do: address
+
+ def maybe_preload_smart_contract_associations(%Address{contract_code: _} = address, associations, options),
+ do: Chain.select_repo(options).preload(address, associations)
+
@doc """
Counts all the addresses where the `fetched_coin_balance` is > 0.
"""
@@ -291,4 +313,77 @@ defmodule Explorer.Chain.Address do
@for.checksum(address)
end
end
+
+ @default_paging_options %PagingOptions{page_size: 50}
+ @doc """
+ Lists the top `t:Explorer.Chain.Address.t/0`'s' in descending order based on coin balance and address hash.
+
+ """
+ @spec list_top_addresses :: [{Address.t(), non_neg_integer()}]
+ def list_top_addresses(options \\ []) do
+ paging_options = Keyword.get(options, :paging_options, @default_paging_options)
+
+ if is_nil(paging_options.key) do
+ paging_options.page_size
+ |> Accounts.take_enough()
+ |> case do
+ nil ->
+ get_addresses(options)
+
+ accounts ->
+ Enum.map(
+ accounts,
+ &{&1, &1.transactions_count || 0}
+ )
+ end
+ else
+ fetch_top_addresses(options)
+ end
+ end
+
+ @doc """
+ Checks if given address is smart-contract
+ """
+ @spec is_smart_contract(any()) :: boolean() | nil
+ def is_smart_contract(%__MODULE__{contract_code: nil}), do: false
+ def is_smart_contract(%__MODULE__{contract_code: _}), do: true
+ def is_smart_contract(%NotLoaded{}), do: nil
+ def is_smart_contract(_), do: false
+
+ defp get_addresses(options) do
+ accounts_with_n = fetch_top_addresses(options)
+
+ accounts_with_n
+ |> Enum.map(fn {address, _n} -> address end)
+ |> Accounts.update()
+
+ accounts_with_n
+ end
+
+ defp fetch_top_addresses(options) do
+ paging_options = Keyword.get(options, :paging_options, @default_paging_options)
+
+ base_query =
+ from(a in Address,
+ where: a.fetched_coin_balance > ^0,
+ order_by: [desc: a.fetched_coin_balance, asc: a.hash],
+ preload: [:names, :smart_contract],
+ select: {a, a.transactions_count}
+ )
+
+ base_query
+ |> page_addresses(paging_options)
+ |> limit(^paging_options.page_size)
+ |> Chain.select_repo(options).all()
+ end
+
+ defp page_addresses(query, %PagingOptions{key: nil}), do: query
+
+ defp page_addresses(query, %PagingOptions{key: {coin_balance, hash}}) do
+ from(address in query,
+ where:
+ (address.fetched_coin_balance == ^coin_balance and address.hash > ^hash) or
+ address.fetched_coin_balance < ^coin_balance
+ )
+ end
end
diff --git a/apps/explorer/lib/explorer/chain/address/coin_balance.ex b/apps/explorer/lib/explorer/chain/address/coin_balance.ex
index 037113c28dcd..b2e5a46f263e 100644
--- a/apps/explorer/lib/explorer/chain/address/coin_balance.ex
+++ b/apps/explorer/lib/explorer/chain/address/coin_balance.ex
@@ -7,7 +7,7 @@ defmodule Explorer.Chain.Address.CoinBalance do
use Explorer.Schema
alias Explorer.PagingOptions
- alias Explorer.Chain.{Address, Block, Hash, Transaction, Wei}
+ alias Explorer.Chain.{Address, Block, Hash, Wei}
alias Explorer.Chain.Address.CoinBalance
@optional_fields ~w(value value_fetched_at)a
@@ -43,7 +43,6 @@ defmodule Explorer.Chain.Address.CoinBalance do
field(:value_fetched_at, :utc_datetime_usec)
field(:delta, Wei, virtual: true)
field(:transaction_hash, Hash.Full, virtual: true)
- field(:transaction_value, Wei, virtual: true)
field(:block_timestamp, :utc_datetime_usec, virtual: true)
timestamps()
@@ -75,35 +74,6 @@ defmodule Explorer.Chain.Address.CoinBalance do
)
end
- @doc """
- Builds an `Ecto.Query` to fetch the last coin balances that have value greater than 0.
-
- The last coin balance from an Address is the last block indexed.
- """
- def fetch_coin_balances_with_txs(address_hash, %PagingOptions{page_size: page_size}) do
- query =
- from(
- cb in CoinBalance,
- left_join: tx in Transaction,
- on:
- cb.block_number == tx.block_number and tx.value > ^0 and
- (cb.address_hash == tx.to_address_hash or cb.address_hash == tx.from_address_hash),
- where: cb.address_hash == ^address_hash,
- where: not is_nil(cb.value),
- order_by: [desc: :block_number],
- select_merge: %{
- delta: fragment("sa0.value - coalesce(lead(sa0.value, 1) over (order by sa0.block_number desc), 0)"),
- transaction_hash: tx.hash,
- transaction_value: tx.value
- }
- )
-
- from(balance in subquery(query),
- where: balance.delta != 0,
- limit: ^page_size
- )
- end
-
def fetch_coin_balances(address_hash, %PagingOptions{page_size: page_size}) do
query =
from(
@@ -112,7 +82,7 @@ defmodule Explorer.Chain.Address.CoinBalance do
where: not is_nil(cb.value),
order_by: [desc: :block_number],
select_merge: %{
- delta: fragment("sa0.value - coalesce(lead(sa0.value, 1) over (order by sa0.block_number desc), 0)")
+ delta: fragment("? - coalesce(lead(?, 1) over (order by ? desc), 0)", cb.value, cb.value, cb.block_number)
}
)
@@ -120,8 +90,7 @@ defmodule Explorer.Chain.Address.CoinBalance do
where: balance.delta != 0,
limit: ^page_size,
select_merge: %{
- transaction_hash: nil,
- transaction_value: nil
+ transaction_hash: nil
}
)
end
@@ -135,19 +104,19 @@ defmodule Explorer.Chain.Address.CoinBalance do
Application.get_env(:block_scout_web, BlockScoutWeb.Chain.Address.CoinBalance)[:coin_balance_history_days]
CoinBalance
- |> join(:inner, [cb], b in Block, on: cb.block_number == b.number)
+ |> join(:inner, [cb], block in Block, on: cb.block_number == block.number)
|> where([cb], cb.address_hash == ^address_hash)
|> limit_time_interval(days_to_consider, block_timestamp)
- |> group_by([cb, b], fragment("date_trunc('day', ?)", b.timestamp))
- |> order_by([cb, b], fragment("date_trunc('day', ?)", b.timestamp))
- |> select([cb, b], %{date: type(fragment("date_trunc('day', ?)", b.timestamp), :date), value: max(cb.value)})
+ |> group_by([cb, block], fragment("date_trunc('day', ?)", block.timestamp))
+ |> order_by([cb, block], fragment("date_trunc('day', ?)", block.timestamp))
+ |> select([cb, block], %{date: type(fragment("date_trunc('day', ?)", block.timestamp), :date), value: max(cb.value)})
end
def limit_time_interval(query, days_to_consider, nil) do
query
|> where(
- [cb, b],
- b.timestamp >=
+ [cb, block],
+ block.timestamp >=
fragment("date_trunc('day', now() - CAST(? AS INTERVAL))", ^%Postgrex.Interval{days: days_to_consider})
)
end
@@ -155,8 +124,8 @@ defmodule Explorer.Chain.Address.CoinBalance do
def limit_time_interval(query, days_to_consider, %{timestamp: timestamp}) do
query
|> where(
- [cb, b],
- b.timestamp >=
+ [cb, block],
+ block.timestamp >=
fragment(
"(? AT TIME ZONE ?) - CAST(? AS INTERVAL)",
^timestamp,
@@ -177,7 +146,7 @@ defmodule Explorer.Chain.Address.CoinBalance do
cb in subquery(coin_balance_query),
inner_join: block in Block,
on: cb.block_number == block.number,
- where: block.consensus,
+ where: block.consensus == true,
select: %{timestamp: block.timestamp, value: cb.value}
)
end
@@ -186,7 +155,6 @@ defmodule Explorer.Chain.Address.CoinBalance do
balance
|> cast(params, @allowed_fields)
|> validate_required(@required_fields)
- |> foreign_key_constraint(:address_hash)
|> unique_constraint(:block_number, name: :address_coin_balances_address_hash_block_number_index)
end
end
diff --git a/apps/explorer/lib/explorer/chain/address/coin_balance_daily.ex b/apps/explorer/lib/explorer/chain/address/coin_balance_daily.ex
index 0aab03e3619a..cc881d9c471d 100644
--- a/apps/explorer/lib/explorer/chain/address/coin_balance_daily.ex
+++ b/apps/explorer/lib/explorer/chain/address/coin_balance_daily.ex
@@ -69,7 +69,6 @@ defmodule Explorer.Chain.Address.CoinBalanceDaily do
balance
|> cast(params, @allowed_fields)
|> validate_required(@required_fields)
- |> foreign_key_constraint(:address_hash)
|> unique_constraint(:day, name: :address_coin_balances_daily_address_hash_day_index)
end
end
diff --git a/apps/explorer/lib/explorer/chain/address/counters.ex b/apps/explorer/lib/explorer/chain/address/counters.ex
new file mode 100644
index 000000000000..9f0aea47d855
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/address/counters.ex
@@ -0,0 +1,584 @@
+defmodule Explorer.Chain.Address.Counters do
+ @moduledoc """
+ Functions related to Explorer.Chain.Address counters
+ """
+ import Ecto.Query, only: [from: 2, limit: 2, select: 3, union: 2, where: 3]
+
+ import Explorer.Chain,
+ only: [select_repo: 1, wrapped_union_subquery: 1]
+
+ alias Explorer.{Chain, Repo}
+
+ alias Explorer.Counters.{
+ AddressesCounter,
+ AddressesWithBalanceCounter,
+ AddressTokenTransfersCounter,
+ AddressTransactionsCounter,
+ AddressTransactionsGasUsageCounter
+ }
+
+ alias Explorer.Chain.{
+ Address,
+ Address.CurrentTokenBalance,
+ Block,
+ Hash,
+ InternalTransaction,
+ Log,
+ TokenTransfer,
+ Transaction,
+ Withdrawal
+ }
+
+ alias Explorer.Chain.Cache.AddressesTabsCounters
+ alias Explorer.Chain.Cache.Helper, as: CacheHelper
+
+ require Logger
+
+ @typep counter :: non_neg_integer() | nil
+
+ @counters_limit 51
+ @types [:validations, :txs, :token_transfers, :token_balances, :logs, :withdrawals, :internal_txs]
+ @txs_types [:txs_from, :txs_to, :txs_contract]
+
+ defp address_hash_to_logs_query(address_hash) do
+ from(l in Log, where: l.address_hash == ^address_hash)
+ end
+
+ defp address_hash_to_validated_blocks_query(address_hash) do
+ from(b in Block, where: b.miner_hash == ^address_hash)
+ end
+
+ def check_if_validated_blocks_at_address(address_hash, options \\ []) do
+ select_repo(options).exists?(address_hash_to_validated_blocks_query(address_hash))
+ end
+
+ def check_if_logs_at_address(address_hash, options \\ []) do
+ select_repo(options).exists?(address_hash_to_logs_query(address_hash))
+ end
+
+ def check_if_token_transfers_at_address(address_hash, options \\ []) do
+ select_repo(options).exists?(from(tt in TokenTransfer, where: tt.from_address_hash == ^address_hash)) ||
+ select_repo(options).exists?(from(tt in TokenTransfer, where: tt.to_address_hash == ^address_hash))
+ end
+
+ def check_if_tokens_at_address(address_hash, options \\ []) do
+ select_repo(options).exists?(address_hash_to_token_balances_query(address_hash))
+ end
+
+ @spec check_if_withdrawals_at_address(Hash.Address.t()) :: boolean()
+ def check_if_withdrawals_at_address(address_hash, options \\ []) do
+ address_hash
+ |> Withdrawal.address_hash_to_withdrawals_unordered_query()
+ |> select_repo(options).exists?()
+ end
+
+ @doc """
+ Gets from the cache the count of `t:Explorer.Chain.Address.t/0`'s where the `fetched_coin_balance` is > 0
+ """
+ @spec count_addresses_with_balance_from_cache :: non_neg_integer()
+ def count_addresses_with_balance_from_cache do
+ AddressesWithBalanceCounter.fetch()
+ end
+
+ @doc """
+ Estimated count of `t:Explorer.Chain.Address.t/0`.
+
+ Estimated count of addresses.
+ """
+ @spec address_estimated_count() :: non_neg_integer()
+ def address_estimated_count(options \\ []) do
+ cached_value = AddressesCounter.fetch()
+
+ if is_nil(cached_value) || cached_value == 0 do
+ count = CacheHelper.estimated_count_from("addresses", options)
+
+ max(count, 0)
+ else
+ cached_value
+ end
+ end
+
+ @doc """
+ Counts the number of all addresses.
+
+ This function should be used with caution. In larger databases, it may take a
+ while to have the return back.
+ """
+ def count_addresses do
+ Repo.aggregate(Address, :count, timeout: :infinity)
+ end
+
+ @doc """
+ Get the total number of transactions sent by the address with the given hash according to the last block indexed.
+
+ We have to increment +1 in the last nonce result because it works like an array position, the first
+ nonce has the value 0. When last nonce is nil, it considers that the given address has 0 transactions.
+ """
+ @spec total_transactions_sent_by_address(Hash.Address.t()) :: non_neg_integer()
+ def total_transactions_sent_by_address(address_hash) do
+ last_nonce =
+ address_hash
+ |> Transaction.last_nonce_by_address_query()
+ |> Repo.one(timeout: :infinity)
+
+ case last_nonce do
+ nil -> 0
+ value -> value + 1
+ end
+ end
+
+ def address_hash_to_transaction_count_query(address_hash) do
+ from(
+ transaction in Transaction,
+ where: transaction.to_address_hash == ^address_hash or transaction.from_address_hash == ^address_hash
+ )
+ end
+
+ @spec address_hash_to_transaction_count(Hash.Address.t()) :: non_neg_integer()
+ def address_hash_to_transaction_count(address_hash) do
+ query = address_hash_to_transaction_count_query(address_hash)
+
+ Repo.aggregate(query, :count, :hash, timeout: :infinity)
+ end
+
+ @spec address_to_transaction_count(Address.t()) :: non_neg_integer()
+ def address_to_transaction_count(address) do
+ address_hash_to_transaction_count(address.hash)
+ end
+
+ @doc """
+ Counts the number of `t:Explorer.Chain.Block.t/0` validated by the address with the given `hash`.
+ """
+ @spec address_to_validation_count(Hash.Address.t(), [Chain.api?()]) :: non_neg_integer()
+ def address_to_validation_count(hash, options) do
+ query = from(block in Block, where: block.miner_hash == ^hash, select: fragment("COUNT(*)"))
+
+ select_repo(options).one(query)
+ end
+
+ @doc """
+ Counts the number of addresses with fetched coin balance > 0.
+
+ This function should be used with caution. In larger databases, it may take a
+ while to have the return back.
+ """
+ def count_addresses_with_balance do
+ Repo.one(
+ Address.count_with_fetched_coin_balance(),
+ timeout: :infinity
+ )
+ end
+
+ @spec address_to_incoming_transaction_count(Hash.Address.t()) :: non_neg_integer()
+ def address_to_incoming_transaction_count(address_hash) do
+ to_address_query =
+ from(
+ transaction in Transaction,
+ where: transaction.to_address_hash == ^address_hash
+ )
+
+ Repo.aggregate(to_address_query, :count, :hash, timeout: :infinity)
+ end
+
+ @spec address_to_incoming_transaction_gas_usage(Hash.Address.t()) :: Decimal.t() | nil
+ def address_to_incoming_transaction_gas_usage(address_hash) do
+ to_address_query =
+ from(
+ transaction in Transaction,
+ where: transaction.to_address_hash == ^address_hash
+ )
+
+ Repo.aggregate(to_address_query, :sum, :gas_used, timeout: :infinity)
+ end
+
+ @spec address_to_outcoming_transaction_gas_usage(Hash.Address.t()) :: Decimal.t() | nil
+ def address_to_outcoming_transaction_gas_usage(address_hash) do
+ to_address_query =
+ from(
+ transaction in Transaction,
+ where: transaction.from_address_hash == ^address_hash
+ )
+
+ Repo.aggregate(to_address_query, :sum, :gas_used, timeout: :infinity)
+ end
+
+ def address_to_token_transfer_count_query(address_hash) do
+ from(
+ token_transfer in TokenTransfer,
+ where: token_transfer.to_address_hash == ^address_hash,
+ or_where: token_transfer.from_address_hash == ^address_hash
+ )
+ end
+
+ @spec address_to_token_transfer_count(Address.t()) :: non_neg_integer()
+ def address_to_token_transfer_count(address) do
+ query = address_to_token_transfer_count_query(address.hash)
+
+ Repo.aggregate(query, :count, timeout: :infinity)
+ end
+
+ def address_hash_to_token_balances_query(address_hash) do
+ from(
+ tb in CurrentTokenBalance,
+ where: tb.address_hash == ^address_hash,
+ where: tb.value > 0
+ )
+ end
+
+ @spec address_to_gas_usage_count(Address.t()) :: Decimal.t() | nil
+ def address_to_gas_usage_count(address) do
+ if Chain.contract?(address) do
+ incoming_transaction_gas_usage = address_to_incoming_transaction_gas_usage(address.hash)
+
+ cond do
+ !incoming_transaction_gas_usage ->
+ address_to_outcoming_transaction_gas_usage(address.hash)
+
+ Decimal.compare(incoming_transaction_gas_usage, 0) == :eq ->
+ address_to_outcoming_transaction_gas_usage(address.hash)
+
+ true ->
+ incoming_transaction_gas_usage
+ end
+ else
+ address_to_outcoming_transaction_gas_usage(address.hash)
+ end
+ end
+
+ defp address_hash_to_internal_txs_limited_count_query(address_hash) do
+ query_to_address_hash_wrapped =
+ InternalTransaction
+ |> InternalTransaction.where_nonpending_block()
+ |> InternalTransaction.where_address_fields_match(address_hash, :to_address_hash)
+ |> InternalTransaction.where_is_different_from_parent_transaction()
+ |> limit(@counters_limit)
+ |> wrapped_union_subquery()
+
+ query_from_address_hash_wrapped =
+ InternalTransaction
+ |> InternalTransaction.where_nonpending_block()
+ |> InternalTransaction.where_address_fields_match(address_hash, :from_address_hash)
+ |> InternalTransaction.where_is_different_from_parent_transaction()
+ |> limit(@counters_limit)
+ |> wrapped_union_subquery()
+
+ query_created_contract_address_hash_wrapped =
+ InternalTransaction
+ |> InternalTransaction.where_nonpending_block()
+ |> InternalTransaction.where_address_fields_match(address_hash, :created_contract_address_hash)
+ |> InternalTransaction.where_is_different_from_parent_transaction()
+ |> limit(@counters_limit)
+ |> wrapped_union_subquery()
+
+ query_to_address_hash_wrapped
+ |> union(^query_from_address_hash_wrapped)
+ |> union(^query_created_contract_address_hash_wrapped)
+ |> wrapped_union_subquery()
+ |> InternalTransaction.where_is_different_from_parent_transaction()
+ |> limit(@counters_limit)
+ end
+
+ def address_counters(address, options \\ []) do
+ validation_count_task =
+ Task.async(fn ->
+ address_to_validation_count(address.hash, options)
+ end)
+
+ Task.start_link(fn ->
+ transaction_count(address)
+ end)
+
+ Task.start_link(fn ->
+ token_transfers_count(address)
+ end)
+
+ Task.start_link(fn ->
+ gas_usage_count(address)
+ end)
+
+ [
+ validation_count_task
+ ]
+ |> Task.yield_many(:infinity)
+ |> Enum.map(fn {_task, res} ->
+ case res do
+ {:ok, result} ->
+ result
+
+ {:exit, reason} ->
+ raise "Query fetching address counters terminated: #{inspect(reason)}"
+
+ nil ->
+ raise "Query fetching address counters timed out."
+ end
+ end)
+ |> List.to_tuple()
+ end
+
+ def transaction_count(address) do
+ AddressTransactionsCounter.fetch(address)
+ end
+
+ def token_transfers_count(address) do
+ AddressTokenTransfersCounter.fetch(address)
+ end
+
+ def gas_usage_count(address) do
+ AddressTransactionsGasUsageCounter.fetch(address)
+ end
+
+ @spec address_limited_counters(Hash.t(), Keyword.t()) ::
+ {counter(), counter(), counter(), counter(), counter(), counter(), counter()}
+ def address_limited_counters(address_hash, options) do
+ cached_counters =
+ Enum.reduce(@types, %{}, fn type, acc ->
+ case AddressesTabsCounters.get_counter(type, address_hash) do
+ {_datetime, counter, status} ->
+ Map.put(acc, type, {status, counter})
+
+ _ ->
+ acc
+ end
+ end)
+
+ start = System.monotonic_time()
+
+ validations_count_task =
+ configure_task(
+ :validations,
+ cached_counters,
+ address_hash_to_validated_blocks_query(address_hash),
+ address_hash,
+ options
+ )
+
+ transactions_from_count_task =
+ run_or_ignore(cached_counters[:txs], :txs_from, address_hash, fn ->
+ result =
+ Transaction
+ |> where([t], t.from_address_hash == ^address_hash)
+ |> Transaction.not_dropped_or_replaced_transactions()
+ |> select([t], t.hash)
+ |> limit(@counters_limit)
+ |> select_repo(options).all()
+
+ stop = System.monotonic_time()
+ diff = System.convert_time_unit(stop - start, :native, :millisecond)
+
+ Logger.info("Time consumed for transactions_from_count_task for #{address_hash} is #{diff}ms")
+
+ AddressesTabsCounters.save_txs_counter_progress(address_hash, %{txs_types: [:txs_from], txs_from: result})
+ AddressesTabsCounters.drop_task(:txs_from, address_hash)
+
+ {:txs_from, result}
+ end)
+
+ transactions_to_count_task =
+ run_or_ignore(cached_counters[:txs], :txs_to, address_hash, fn ->
+ result =
+ Transaction
+ |> where([t], t.to_address_hash == ^address_hash)
+ |> Transaction.not_dropped_or_replaced_transactions()
+ |> select([t], t.hash)
+ |> limit(@counters_limit)
+ |> select_repo(options).all()
+
+ stop = System.monotonic_time()
+ diff = System.convert_time_unit(stop - start, :native, :millisecond)
+
+ Logger.info("Time consumed for transactions_to_count_task for #{address_hash} is #{diff}ms")
+
+ AddressesTabsCounters.save_txs_counter_progress(address_hash, %{txs_types: [:txs_to], txs_to: result})
+ AddressesTabsCounters.drop_task(:txs_to, address_hash)
+
+ {:txs_to, result}
+ end)
+
+ transactions_created_contract_count_task =
+ run_or_ignore(cached_counters[:txs], :txs_contract, address_hash, fn ->
+ result =
+ Transaction
+ |> where([t], t.created_contract_address_hash == ^address_hash)
+ |> Transaction.not_dropped_or_replaced_transactions()
+ |> select([t], t.hash)
+ |> limit(@counters_limit)
+ |> select_repo(options).all()
+
+ stop = System.monotonic_time()
+ diff = System.convert_time_unit(stop - start, :native, :millisecond)
+
+ Logger.info("Time consumed for transactions_created_contract_count_task for #{address_hash} is #{diff}ms")
+
+ AddressesTabsCounters.save_txs_counter_progress(address_hash, %{
+ txs_types: [:txs_contract],
+ txs_contract: result
+ })
+
+ AddressesTabsCounters.drop_task(:txs_contract, address_hash)
+
+ {:txs_contract, result}
+ end)
+
+ token_transfers_count_task =
+ configure_task(
+ :token_transfers,
+ cached_counters,
+ address_to_token_transfer_count_query(address_hash),
+ address_hash,
+ options
+ )
+
+ token_balances_count_task =
+ configure_task(
+ :token_balances,
+ cached_counters,
+ address_hash_to_token_balances_query(address_hash),
+ address_hash,
+ options
+ )
+
+ logs_count_task =
+ configure_task(
+ :logs,
+ cached_counters,
+ address_hash_to_logs_query(address_hash),
+ address_hash,
+ options
+ )
+
+ withdrawals_count_task =
+ configure_task(
+ :withdrawals,
+ cached_counters,
+ Withdrawal.address_hash_to_withdrawals_unordered_query(address_hash),
+ address_hash,
+ options
+ )
+
+ internal_txs_count_task =
+ configure_task(
+ :internal_txs,
+ cached_counters,
+ address_hash_to_internal_txs_limited_count_query(address_hash),
+ address_hash,
+ options
+ )
+
+ map =
+ [
+ validations_count_task,
+ transactions_from_count_task,
+ transactions_to_count_task,
+ transactions_created_contract_count_task,
+ token_transfers_count_task,
+ token_balances_count_task,
+ logs_count_task,
+ withdrawals_count_task,
+ internal_txs_count_task
+ ]
+ |> Enum.reject(&is_nil/1)
+ |> Task.yield_many(:timer.seconds(1))
+ |> Enum.reduce(Map.merge(prepare_cache_values(cached_counters), %{txs_types: [], txs_hashes: []}), fn {task, res},
+ acc ->
+ case res do
+ {:ok, {txs_type, txs_hashes}} when txs_type in @txs_types ->
+ acc
+ |> (&Map.put(&1, :txs_types, [txs_type | &1[:txs_types] || []])).()
+ |> (&Map.put(&1, :txs_hashes, &1[:txs_hashes] ++ txs_hashes)).()
+
+ {:ok, {type, counter}} ->
+ Map.put(acc, type, counter)
+
+ {:exit, reason} ->
+ Logger.warn(fn ->
+ [
+ "Query fetching address counters for #{address_hash} terminated: #{inspect(reason)}"
+ ]
+ end)
+
+ acc
+
+ nil ->
+ Logger.warn(fn ->
+ [
+ "Query fetching address counters for #{address_hash} timed out."
+ ]
+ end)
+
+ Task.ignore(task)
+
+ acc
+ end
+ end)
+ |> process_txs_counter()
+
+ {map[:validations], map[:txs], map[:token_transfers], map[:token_balances], map[:logs], map[:withdrawals],
+ map[:internal_txs]}
+ end
+
+ defp run_or_ignore({ok, _counter}, _type, _address_hash, _fun) when ok in [:up_to_date, :limit_value], do: nil
+
+ defp run_or_ignore(_, type, address_hash, fun) do
+ if !AddressesTabsCounters.get_task(type, address_hash) do
+ AddressesTabsCounters.set_task(type, address_hash)
+
+ Task.async(fun)
+ end
+ end
+
+ defp configure_task(counter_type, cache, query, address_hash, options) do
+ address_hash = to_string(address_hash)
+ start = System.monotonic_time()
+
+ run_or_ignore(cache[counter_type], counter_type, address_hash, fn ->
+ result =
+ query
+ |> limit(@counters_limit)
+ |> select_repo(options).aggregate(:count)
+
+ stop = System.monotonic_time()
+ diff = System.convert_time_unit(stop - start, :native, :millisecond)
+
+ Logger.info("Time consumed for #{counter_type} counter task for #{address_hash} is #{diff}ms")
+
+ AddressesTabsCounters.set_counter(counter_type, address_hash, result)
+ AddressesTabsCounters.drop_task(counter_type, address_hash)
+
+ {counter_type, result}
+ end)
+ end
+
+ defp process_txs_counter(%{txs_types: [_ | _] = txs_types, txs_hashes: hashes} = map) do
+ counter = hashes |> Enum.uniq() |> Enum.count() |> min(@counters_limit)
+
+ if Enum.count(txs_types) == 3 || counter == @counters_limit do
+ map |> Map.put(:txs, counter)
+ else
+ map
+ end
+ end
+
+ defp process_txs_counter(map), do: map
+
+ defp prepare_cache_values(cached_counters) do
+ Enum.reduce(cached_counters, %{}, fn
+ {k, {_, counter}}, acc ->
+ Map.put(acc, k, counter)
+
+ {k, v}, acc ->
+ Map.put(acc, k, v)
+ end)
+ end
+
+ @doc """
+ Returns all possible transactions type
+ """
+ @spec txs_types :: list(atom)
+ def txs_types, do: @txs_types
+
+ @doc """
+ Returns max counter value
+ """
+ @spec counters_limit :: integer()
+ def counters_limit, do: @counters_limit
+end
diff --git a/apps/explorer/lib/explorer/chain/address/current_token_balance.ex b/apps/explorer/lib/explorer/chain/address/current_token_balance.ex
index 109405354d0d..fe7f3070122c 100644
--- a/apps/explorer/lib/explorer/chain/address/current_token_balance.ex
+++ b/apps/explorer/lib/explorer/chain/address/current_token_balance.ex
@@ -10,6 +10,7 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
import Ecto.Changeset
import Ecto.Query, only: [from: 2, limit: 2, offset: 2, order_by: 3, preload: 2, dynamic: 2]
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
alias Explorer.{Chain, PagingOptions, Repo}
alias Explorer.Chain.{Address, Block, Hash, Token}
@@ -37,7 +38,10 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
updated_at: DateTime.t(),
value: Decimal.t() | nil,
token_id: non_neg_integer() | nil,
- token_type: String.t()
+ token_type: String.t(),
+ distinct_token_instances_count: non_neg_integer(),
+ token_ids: list(Decimal.t()),
+ preloaded_token_instances: list()
}
schema "address_current_token_balances" do
@@ -47,6 +51,10 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
field(:value_fetched_at, :utc_datetime_usec)
field(:token_id, :decimal)
field(:token_type, :string)
+ field(:fiat_value, :decimal, virtual: true)
+ field(:distinct_token_instances_count, :integer, virtual: true)
+ field(:token_ids, {:array, :decimal}, virtual: true)
+ field(:preloaded_token_instances, {:array, :any}, virtual: true)
# A transient field for deriving token holder count deltas during address_current_token_balances upserts
field(:old_value, :decimal)
@@ -73,11 +81,9 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
token_balance
|> cast(attrs, @allowed_fields)
|> validate_required(@required_fields)
- |> foreign_key_constraint(:address_hash)
- |> foreign_key_constraint(:token_contract_address_hash)
end
- {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
+ {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string())
@burn_address_hash burn_address_hash
@doc """
@@ -157,6 +163,25 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
dynamic([ctb, t], ctb.value * t.fiat_value / fragment("10 ^ ?", t.decimals))
end
+ @doc """
+ Builds an `t:Ecto.Query.t/0` to fetch the current token balances of the given address (include unfetched).
+ """
+ def last_token_balances_include_unfetched(address_hash) do
+ fiat_balance = fiat_value_query()
+
+ from(
+ ctb in __MODULE__,
+ where: ctb.address_hash == ^address_hash,
+ left_join: t in assoc(ctb, :token),
+ on: ctb.token_contract_address_hash == t.contract_address_hash,
+ preload: [token: t],
+ select: ctb,
+ select_merge: ^%{fiat_value: fiat_balance},
+ order_by: ^[desc_nulls_last: fiat_balance],
+ order_by: [desc: ctb.value, desc: ctb.id]
+ )
+ end
+
@doc """
Builds an `t:Ecto.Query.t/0` to fetch the current token balances of the given address.
"""
@@ -169,10 +194,12 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
ctb in __MODULE__,
where: ctb.address_hash == ^address_hash,
where: ctb.value > 0,
- where: ctb.token_type == ^type,
- left_join: t in Token,
+ left_join: t in assoc(ctb, :token),
on: ctb.token_contract_address_hash == t.contract_address_hash,
- select: {ctb, t},
+ preload: [token: t],
+ where: t.type == ^type,
+ select: ctb,
+ select_merge: ^%{fiat_value: fiat_balance},
order_by: ^[desc_nulls_last: fiat_balance],
order_by: [desc: ctb.value, desc: ctb.id]
)
@@ -185,9 +212,11 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
ctb in __MODULE__,
where: ctb.address_hash == ^address_hash,
where: ctb.value > 0,
- left_join: t in Token,
+ left_join: t in assoc(ctb, :token),
on: ctb.token_contract_address_hash == t.contract_address_hash,
- select: {ctb, t},
+ preload: [token: t],
+ select: ctb,
+ select_merge: ^%{fiat_value: fiat_balance},
order_by: ^[desc_nulls_last: fiat_balance],
order_by: [desc: ctb.value, desc: ctb.id]
)
diff --git a/apps/explorer/lib/explorer/chain/address/name.ex b/apps/explorer/lib/explorer/chain/address/name.ex
index 4364adf98795..533ef2878911 100644
--- a/apps/explorer/lib/explorer/chain/address/name.ex
+++ b/apps/explorer/lib/explorer/chain/address/name.ex
@@ -7,9 +7,11 @@ defmodule Explorer.Chain.Address.Name do
import Ecto.Changeset
- alias Ecto.Changeset
+ alias Ecto.{Changeset, Repo}
alias Explorer.Chain.{Address, Hash}
+ import Ecto.Query, only: [from: 2]
+
@typedoc """
* `address` - the `t:Explorer.Chain.Address.t/0` with `value` at end of `block_number`.
* `address_hash` - foreign key for `address`.
@@ -46,6 +48,46 @@ defmodule Explorer.Chain.Address.Name do
|> foreign_key_constraint(:address_hash)
end
+ @doc """
+ Sets primary false for all primary names for the given address hash
+ """
+ @spec clear_primary_address_names(Repo.t(), Hash.Address.t()) :: {:ok, []}
+ def clear_primary_address_names(repo, address_hash) do
+ query =
+ from(
+ address_name in __MODULE__,
+ where: address_name.address_hash == ^address_hash,
+ where: address_name.primary == true,
+ # Enforce Name ShareLocks order (see docs: sharelocks.md)
+ order_by: [asc: :address_hash, asc: :name],
+ lock: "FOR NO KEY UPDATE"
+ )
+
+ repo.update_all(
+ from(n in __MODULE__, join: s in subquery(query), on: n.address_hash == s.address_hash and n.name == s.name),
+ set: [primary: false]
+ )
+
+ {:ok, []}
+ end
+
+ @doc """
+ Creates primary address name for the given address hash
+ """
+ @spec create_primary_address_name(Repo.t(), String.t(), Hash.Address.t()) ::
+ {:ok, [__MODULE__.t()]} | {:error, [Changeset.t()]}
+ def create_primary_address_name(repo, name, address_hash) do
+ params = %{
+ address_hash: address_hash,
+ name: name,
+ primary: true
+ }
+
+ %__MODULE__{}
+ |> changeset(params)
+ |> repo.insert(on_conflict: :nothing, conflict_target: [:address_hash, :name])
+ end
+
defp trim_name(%Changeset{valid?: false} = changeset), do: changeset
defp trim_name(%Changeset{valid?: true} = changeset) do
diff --git a/apps/explorer/lib/explorer/chain/address/token_balance.ex b/apps/explorer/lib/explorer/chain/address/token_balance.ex
index 296a646af14a..5b647bae1ed2 100644
--- a/apps/explorer/lib/explorer/chain/address/token_balance.ex
+++ b/apps/explorer/lib/explorer/chain/address/token_balance.ex
@@ -9,8 +9,11 @@ defmodule Explorer.Chain.Address.TokenBalance do
use Explorer.Schema
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
+
alias Explorer.Chain
alias Explorer.Chain.Address.TokenBalance
+ alias Explorer.Chain.Cache.BackgroundMigrations
alias Explorer.Chain.{Address, Block, Hash, Token}
@typedoc """
@@ -65,12 +68,10 @@ defmodule Explorer.Chain.Address.TokenBalance do
token_balance
|> cast(attrs, @allowed_fields)
|> validate_required(@required_fields)
- |> foreign_key_constraint(:address_hash)
- |> foreign_key_constraint(:token_contract_address_hash)
|> unique_constraint(:block_number, name: :token_balances_address_hash_block_number_index)
end
- {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
+ {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string())
@burn_address_hash burn_address_hash
@doc """
@@ -80,25 +81,51 @@ defmodule Explorer.Chain.Address.TokenBalance do
ignores the burn_address for tokens ERC-721 since the most tokens ERC-721 don't allow get the
balance for burn_address.
"""
+ # credo:disable-for-next-line /Complexity/
def unfetched_token_balances do
- from(
- tb in TokenBalance,
- join: t in Token,
- on: tb.token_contract_address_hash == t.contract_address_hash,
- where:
- ((tb.address_hash != ^@burn_address_hash and t.type == "ERC-721") or t.type == "ERC-20" or t.type == "ERC-1155") and
- (is_nil(tb.value_fetched_at) or is_nil(tb.value))
- )
+ if BackgroundMigrations.get_tb_token_type_finished() do
+ from(
+ tb in TokenBalance,
+ where:
+ ((tb.address_hash != ^@burn_address_hash and tb.token_type == "ERC-721") or tb.token_type == "ERC-20" or
+ tb.token_type == "ERC-1155") and
+ (is_nil(tb.value_fetched_at) or is_nil(tb.value))
+ )
+ else
+ from(
+ tb in TokenBalance,
+ join: t in Token,
+ on: tb.token_contract_address_hash == t.contract_address_hash,
+ where:
+ ((tb.address_hash != ^@burn_address_hash and t.type == "ERC-721") or t.type == "ERC-20" or
+ t.type == "ERC-1155") and
+ (is_nil(tb.value_fetched_at) or is_nil(tb.value))
+ )
+ end
end
@doc """
Builds an `Ecto.Query` to fetch the token balance of the given token contract hash of the given address in the given block.
"""
- def fetch_token_balance(address_hash, token_contract_address_hash, block_number) do
+ def fetch_token_balance(address_hash, token_contract_address_hash, block_number, token_id \\ nil)
+
+ def fetch_token_balance(address_hash, token_contract_address_hash, block_number, nil) do
+ from(
+ tb in TokenBalance,
+ where: tb.address_hash == ^address_hash,
+ where: tb.token_contract_address_hash == ^token_contract_address_hash,
+ where: tb.block_number <= ^block_number,
+ limit: ^1,
+ order_by: [desc: :block_number]
+ )
+ end
+
+ def fetch_token_balance(address_hash, token_contract_address_hash, block_number, token_id) do
from(
tb in TokenBalance,
where: tb.address_hash == ^address_hash,
where: tb.token_contract_address_hash == ^token_contract_address_hash,
+ where: tb.token_id == ^token_id,
where: tb.block_number <= ^block_number,
limit: ^1,
order_by: [desc: :block_number]
diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex
index 8b1334a357b3..fae356f3bcfa 100644
--- a/apps/explorer/lib/explorer/chain/block.ex
+++ b/apps/explorer/lib/explorer/chain/block.ex
@@ -7,10 +7,19 @@ defmodule Explorer.Chain.Block do
use Explorer.Schema
- alias Explorer.Chain.{Address, Gas, Hash, PendingBlockOperation, Transaction, Wei, Withdrawal}
- alias Explorer.Chain.Block.{Reward, SecondDegreeRelation}
+ alias Explorer.Chain.{Address, Block, Gas, Hash, PendingBlockOperation, Transaction, Wei, Withdrawal}
+ alias Explorer.Chain.Block.{EmissionReward, Reward, SecondDegreeRelation}
+ alias Explorer.Repo
@optional_attrs ~w(size refetch_needed total_difficulty difficulty base_fee_per_gas)a
+ |> (&(case Application.compile_env(:explorer, :chain_type) do
+ "rsk" ->
+ &1 ++
+ ~w(minimum_gas_price bitcoin_merged_mining_header bitcoin_merged_mining_coinbase_transaction bitcoin_merged_mining_merkle_proof hash_for_merged_mining)a
+
+ _ ->
+ &1
+ end)).()
@required_attrs ~w(consensus gas_limit gas_used hash miner_hash nonce number parent_hash timestamp)a
@@ -26,6 +35,20 @@ defmodule Explorer.Chain.Block do
"""
@type block_number :: non_neg_integer()
+ if Application.compile_env(:explorer, :chain_type) == "rsk" do
+ @rootstock_fields quote(
+ do: [
+ bitcoin_merged_mining_header: binary(),
+ bitcoin_merged_mining_coinbase_transaction: binary(),
+ bitcoin_merged_mining_merkle_proof: binary(),
+ hash_for_merged_mining: binary(),
+ minimum_gas_price: Decimal.t()
+ ]
+ )
+ else
+ @rootstock_fields quote(do: [])
+ end
+
@typedoc """
* `consensus`
* `true` - this is a block on the longest consensus agreed upon chain.
@@ -47,8 +70,18 @@ defmodule Explorer.Chain.Block do
* `total_difficulty` - the total `difficulty` of the chain until this block.
* `transactions` - the `t:Explorer.Chain.Transaction.t/0` in this block.
* `base_fee_per_gas` - Minimum fee required per unit of gas. Fee adjusts based on network congestion.
+ #{if Application.compile_env(:explorer, :chain_type) == "rsk" do
+ """
+ * `bitcoin_merged_mining_header` - Bitcoin merged mining header on Rootstock chains.
+ * `bitcoin_merged_mining_coinbase_transaction` - Bitcoin merged mining coinbase transaction on Rootstock chains.
+ * `bitcoin_merged_mining_merkle_proof` - Bitcoin merged mining merkle proof on Rootstock chains.
+ * `hash_for_merged_mining` - Hash for merged mining on Rootstock chains.
+ * `minimum_gas_price` - Minimum block gas price on Rootstock chains.
+ """
+ end}
"""
@type t :: %__MODULE__{
+ unquote_splicing(@rootstock_fields),
consensus: boolean(),
difficulty: difficulty(),
gas_limit: Gas.t(),
@@ -83,6 +116,14 @@ defmodule Explorer.Chain.Block do
field(:base_fee_per_gas, Wei)
field(:is_empty, :boolean)
+ if Application.compile_env(:explorer, :chain_type) == "rsk" do
+ field(:bitcoin_merged_mining_header, :binary)
+ field(:bitcoin_merged_mining_coinbase_transaction, :binary)
+ field(:bitcoin_merged_mining_merkle_proof, :binary)
+ field(:hash_for_merged_mining, :binary)
+ field(:minimum_gas_price, :decimal)
+ end
+
timestamps()
belongs_to(:miner, Address, foreign_key: :miner_hash, references: :hash, type: Hash.Address)
@@ -149,10 +190,119 @@ defmodule Explorer.Chain.Block do
def block_type_filter(query, "Block"), do: where(query, [block], block.consensus == true)
def block_type_filter(query, "Reorg") do
- query
- |> join(:left, [block], uncles in assoc(block, :nephew_relations))
- |> where([block, uncles], block.consensus == false and is_nil(uncles.uncle_hash))
+ from(block in query,
+ as: :block,
+ left_join: uncles in assoc(block, :nephew_relations),
+ where:
+ block.consensus == false and is_nil(uncles.uncle_hash) and
+ exists(from(b in Block, where: b.number == parent_as(:block).number and b.consensus))
+ )
end
def block_type_filter(query, "Uncle"), do: where(query, [block], block.consensus == false)
+
+ @doc """
+ Returns query that fetches up to `limit` of consensus blocks
+ that are missing rootstock data ordered by number desc.
+ """
+ @spec blocks_without_rootstock_data_query(non_neg_integer()) :: Ecto.Query.t()
+ def blocks_without_rootstock_data_query(limit) do
+ from(
+ block in __MODULE__,
+ where:
+ is_nil(block.minimum_gas_price) or
+ is_nil(block.bitcoin_merged_mining_header) or
+ is_nil(block.bitcoin_merged_mining_coinbase_transaction) or
+ is_nil(block.bitcoin_merged_mining_merkle_proof) or
+ is_nil(block.hash_for_merged_mining),
+ where: block.consensus == true,
+ limit: ^limit,
+ order_by: [desc: block.number]
+ )
+ end
+
+ @doc """
+ Calculates transaction fees (gas price * gas used) for the list of transactions (from a single block)
+ """
+ @spec transaction_fees([Transaction.t()]) :: Decimal.t()
+ def transaction_fees(transactions) do
+ Enum.reduce(transactions, Decimal.new(0), fn %{gas_used: gas_used, gas_price: gas_price}, acc ->
+ if gas_price do
+ gas_used
+ |> Decimal.new()
+ |> Decimal.mult(gas_price_to_decimal(gas_price))
+ |> Decimal.add(acc)
+ else
+ acc
+ end
+ end)
+ end
+
+ defp gas_price_to_decimal(nil), do: nil
+ defp gas_price_to_decimal(%Wei{} = wei), do: wei.value
+ defp gas_price_to_decimal(gas_price), do: Decimal.new(gas_price)
+
+ @doc """
+ Calculates burnt fees for the list of transactions (from a single block)
+ """
+ @spec burnt_fees(list(), Wei.t() | nil) :: Wei.t() | nil
+ def burnt_fees(transactions, base_fee_per_gas) do
+ total_gas_used =
+ transactions
+ |> Enum.reduce(Decimal.new(0), fn %{gas_used: gas_used}, acc ->
+ gas_used
+ |> Decimal.new()
+ |> Decimal.add(acc)
+ end)
+
+ if is_nil(base_fee_per_gas) do
+ nil
+ else
+ Wei.mult(base_fee_per_gas_to_wei(base_fee_per_gas), total_gas_used)
+ end
+ end
+
+ defp base_fee_per_gas_to_wei(%Wei{} = wei), do: wei
+ defp base_fee_per_gas_to_wei(base_fee_per_gas), do: %Wei{value: Decimal.new(base_fee_per_gas)}
+
+ @uncle_reward_coef 1 / 32
+ @spec block_reward_by_parts(Block.t(), [Transaction.t()]) :: %{
+ block_number: block_number(),
+ block_hash: Hash.Full.t(),
+ miner_hash: Hash.Address.t(),
+ static_reward: any(),
+ transaction_fees: any(),
+ burnt_fees: Wei.t() | nil,
+ uncle_reward: Wei.t() | nil | false
+ }
+ def block_reward_by_parts(block, transactions) do
+ %{hash: block_hash, number: block_number} = block
+ base_fee_per_gas = Map.get(block, :base_fee_per_gas)
+
+ transaction_fees = transaction_fees(transactions)
+
+ static_reward =
+ Repo.one(
+ from(
+ er in EmissionReward,
+ where: fragment("int8range(?, ?) <@ ?", ^block_number, ^(block_number + 1), er.block_range),
+ select: er.reward
+ )
+ ) || %Wei{value: Decimal.new(0)}
+
+ has_uncles? = is_list(block.uncles) and not Enum.empty?(block.uncles)
+
+ burnt_fees = burnt_fees(transactions, base_fee_per_gas)
+ uncle_reward = (has_uncles? && Wei.mult(static_reward, Decimal.from_float(@uncle_reward_coef))) || nil
+
+ %{
+ block_number: block_number,
+ block_hash: block_hash,
+ miner_hash: block.miner_hash,
+ static_reward: static_reward,
+ transaction_fees: %Wei{value: transaction_fees},
+ burnt_fees: burnt_fees || %Wei{value: Decimal.new(0)},
+ uncle_reward: uncle_reward || %Wei{value: Decimal.new(0)}
+ }
+ end
end
diff --git a/apps/explorer/lib/explorer/chain/block/reward.ex b/apps/explorer/lib/explorer/chain/block/reward.ex
index b57af7a8cfa1..d45df2666669 100644
--- a/apps/explorer/lib/explorer/chain/block/reward.ex
+++ b/apps/explorer/lib/explorer/chain/block/reward.ex
@@ -6,10 +6,11 @@ defmodule Explorer.Chain.Block.Reward do
use Explorer.Schema
alias Explorer.Application.Constants
- alias Explorer.{Chain, PagingOptions}
+ alias Explorer.{Chain, PagingOptions, Repo}
alias Explorer.Chain.Block.Reward.AddressType
alias Explorer.Chain.{Address, Block, Hash, Validator, Wei}
alias Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand
+ alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.Reader
@required_attrs ~w(address_hash address_type block_hash reward)a
@@ -34,8 +35,6 @@ defmodule Explorer.Chain.Block.Reward do
"constant" => true
}
- @empty_address "0x0000000000000000000000000000000000000000"
-
@typedoc """
The validation reward given related to a block.
@@ -218,7 +217,7 @@ defmodule Explorer.Chain.Block.Reward do
payout_key_hash =
call_contract(keys_manager_contract_address, @get_payout_by_mining_abi, get_payout_by_mining_params)
- if payout_key_hash == @empty_address do
+ if payout_key_hash == SmartContract.burn_address_hash_string() do
mining_key
else
choose_key(payout_key_hash, mining_key)
@@ -248,7 +247,7 @@ defmodule Explorer.Chain.Block.Reward do
case Reader.query_contract(address, abi, params, false) do
%{^method_id => {:ok, [result]}} -> result
- _ -> @empty_address
+ _ -> SmartContract.burn_address_hash_string()
end
end
@@ -279,4 +278,14 @@ defmodule Explorer.Chain.Block.Reward do
query
end
end
+
+ @doc """
+ Checks if an address has rewards
+ """
+ @spec address_has_rewards?(Hash.Address.t()) :: boolean()
+ def address_has_rewards?(address_hash) do
+ query = from(r in __MODULE__, where: r.address_hash == ^address_hash)
+
+ Repo.exists?(query)
+ end
end
diff --git a/apps/explorer/lib/explorer/chain/cache/addresses_tabs_counters.ex b/apps/explorer/lib/explorer/chain/cache/addresses_tabs_counters.ex
new file mode 100644
index 000000000000..ab79fbf0a5f2
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/cache/addresses_tabs_counters.ex
@@ -0,0 +1,135 @@
+defmodule Explorer.Chain.Cache.AddressesTabsCounters do
+ @moduledoc """
+ Cache for tabs counters on address
+ """
+
+ use GenServer
+
+ import Explorer.Counters.Helper, only: [fetch_from_cache: 3]
+
+ alias Explorer.Chain.Address.Counters
+
+ @cache_name :addresses_tabs_counters
+
+ @typep counter_type :: :validations | :txs | :token_transfers | :token_balances | :logs | :withdrawals | :internal_txs
+ @typep response_status :: :limit_value | :stale | :up_to_date
+
+ @spec get_counter(counter_type, String.t()) :: {DateTime.t(), non_neg_integer(), response_status} | nil
+ def get_counter(counter_type, address_hash) do
+ address_hash |> cache_key(counter_type) |> fetch_from_cache(@cache_name, nil) |> check_staleness()
+ end
+
+ @spec set_counter(counter_type, String.t(), non_neg_integer()) :: :ok
+ def set_counter(counter_type, address_hash, counter, need_to_modify_state? \\ true) do
+ :ets.insert(@cache_name, {cache_key(address_hash, counter_type), {DateTime.utc_now(), counter}})
+ if need_to_modify_state?, do: ignore_txs(counter_type, address_hash)
+
+ :ok
+ end
+
+ @spec set_task(atom, String.t()) :: true
+ def set_task(counter_type, address_hash) do
+ :ets.insert(@cache_name, {task_cache_key(address_hash, counter_type), true})
+ end
+
+ @spec drop_task(atom, String.t()) :: true
+ def drop_task(counter_type, address_hash) do
+ :ets.delete(@cache_name, task_cache_key(address_hash, counter_type))
+ end
+
+ @spec get_task(atom, String.t()) :: true | nil
+ def get_task(counter_type, address_hash) do
+ address_hash |> task_cache_key(counter_type) |> fetch_from_cache(@cache_name, nil)
+ end
+
+ @spec ignore_txs(atom, String.t()) :: :ignore | :ok
+ def ignore_txs(:txs, address_hash), do: GenServer.cast(__MODULE__, {:ignore_txs, address_hash})
+ def ignore_txs(_counter_type, _address_hash), do: :ignore
+
+ def save_txs_counter_progress(address_hash, results) do
+ GenServer.cast(__MODULE__, {:set_txs_state, address_hash, results})
+ end
+
+ def start_link(_) do
+ GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
+ end
+
+ @impl true
+ def init(_opts) do
+ :ets.new(@cache_name, [
+ :set,
+ :named_table,
+ :public,
+ read_concurrency: true,
+ write_concurrency: true
+ ])
+
+ {:ok, %{}}
+ end
+
+ @impl true
+ def handle_cast({:ignore_txs, address_hash}, state) do
+ {:noreply, Map.put(state, lowercased_string(address_hash), {:updated, DateTime.utc_now()})}
+ end
+
+ @impl true
+ def handle_cast({:set_txs_state, address_hash, %{txs_types: txs_types} = results}, state) do
+ address_hash = lowercased_string(address_hash)
+
+ if is_ignored?(state[address_hash]) do
+ {:noreply, state}
+ else
+ address_state =
+ txs_types
+ |> Enum.reduce(state[address_hash] || %{}, fn tx_type, acc ->
+ Map.put(acc, tx_type, results[tx_type])
+ end)
+ |> (&Map.put(&1, :txs_types, (txs_types ++ (&1[:txs_types] || [])) |> Enum.uniq())).()
+
+ counter =
+ Counters.txs_types()
+ |> Enum.reduce([], fn type, acc ->
+ (address_state[type] || []) ++ acc
+ end)
+ |> Enum.uniq()
+ |> Enum.count()
+ |> min(Counters.counters_limit())
+
+ if counter == Counters.counters_limit() || Enum.count(address_state[:txs_types]) == 3 do
+ set_counter(:txs, address_hash, counter, false)
+ {:noreply, Map.put(state, address_hash, {:updated, DateTime.utc_now()})}
+ else
+ {:noreply, Map.put(state, address_hash, address_state)}
+ end
+ end
+ end
+
+ defp is_ignored?({:updated, datetime}), do: is_up_to_date?(datetime, ttl())
+ defp is_ignored?(_), do: false
+
+ defp check_staleness(nil), do: nil
+ defp check_staleness({datetime, counter}) when counter > 50, do: {datetime, counter, :limit_value}
+
+ defp check_staleness({datetime, counter}) do
+ status =
+ if is_up_to_date?(datetime, ttl()) do
+ :up_to_date
+ else
+ :stale
+ end
+
+ {datetime, counter, status}
+ end
+
+ defp is_up_to_date?(datetime, ttl) do
+ datetime
+ |> DateTime.add(ttl, :millisecond)
+ |> DateTime.compare(DateTime.utc_now()) != :lt
+ end
+
+ defp ttl, do: Application.get_env(:explorer, Explorer.Chain.Cache.AddressesTabsCounters)[:ttl]
+ defp lowercased_string(str), do: str |> to_string() |> String.downcase()
+
+ defp cache_key(address_hash, counter_type), do: {lowercased_string(address_hash), counter_type}
+ defp task_cache_key(address_hash, counter_type), do: {:task, lowercased_string(address_hash), counter_type}
+end
diff --git a/apps/explorer/lib/explorer/chain/cache/background_migrations.ex b/apps/explorer/lib/explorer/chain/cache/background_migrations.ex
new file mode 100644
index 000000000000..3470f48c08b7
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/cache/background_migrations.ex
@@ -0,0 +1,45 @@
+defmodule Explorer.Chain.Cache.BackgroundMigrations do
+ @moduledoc """
+ Caches background migrations' status.
+ """
+
+ require Logger
+
+ use Explorer.Chain.MapCache,
+ name: :background_migrations_status,
+ key: :denormalization_finished,
+ key: :tb_token_type_finished,
+ key: :ctb_token_type_finished
+
+ @dialyzer :no_match
+
+ alias Explorer.Migrator.{
+ AddressCurrentTokenBalanceTokenType,
+ AddressTokenBalanceTokenType,
+ TransactionsDenormalization
+ }
+
+ defp handle_fallback(:denormalization_finished) do
+ Task.start(fn ->
+ set_denormalization_finished(TransactionsDenormalization.migration_finished?())
+ end)
+
+ {:return, false}
+ end
+
+ defp handle_fallback(:tb_token_type_finished) do
+ Task.start(fn ->
+ set_tb_token_type_finished(AddressTokenBalanceTokenType.migration_finished?())
+ end)
+
+ {:return, false}
+ end
+
+ defp handle_fallback(:ctb_token_type_finished) do
+ Task.start(fn ->
+ set_ctb_token_type_finished(AddressCurrentTokenBalanceTokenType.migration_finished?())
+ end)
+
+ {:return, false}
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex
index 4315bd7b5d1a..d09ce6d41cb0 100644
--- a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex
+++ b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex
@@ -10,59 +10,348 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do
from: 2
]
+ alias EthereumJSONRPC.Blocks
+
alias Explorer.Chain.{
Block,
+ DenormalizationHelper,
+ Transaction,
Wei
}
- alias Explorer.Repo
+ alias Explorer.Counters.AverageBlockTime
+ alias Explorer.{Market, Repo}
+ alias Timex.Duration
use Explorer.Chain.MapCache,
name: :gas_price,
key: :gas_prices,
+ key: :gas_prices_acc,
+ key: :updated_at,
+ key: :old_gas_prices,
+ key: :old_updated_at,
key: :async_task,
- global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl],
+ global_ttl: :infinity,
ttl_check_interval: :timer.seconds(1),
callback: &async_task_on_deletion(&1)
+ @doc """
+ Calculates how much time left till the next gas prices updated taking into account estimated query running time.
+ """
+ @spec update_in :: non_neg_integer()
+ def update_in do
+ case {get_old_updated_at(), get_updated_at()} do
+ {%DateTime{} = old_updated_at, %DateTime{} = updated_at} ->
+ time_to_update = DateTime.diff(updated_at, old_updated_at, :millisecond) + 500
+ time_since_last_update = DateTime.diff(DateTime.utc_now(), updated_at, :millisecond)
+ next_update_in = time_to_update - time_since_last_update
+ if next_update_in <= 0, do: global_ttl(), else: next_update_in
+
+ _ ->
+ global_ttl() + :timer.seconds(2)
+ end
+ end
+
+ @doc """
+ Calculates the `slow`, `average`, and `fast` gas price and time percentiles from the last `num_of_blocks` blocks and estimates the fiat price for each percentile.
+ These percentiles correspond to the likelihood of a transaction being picked up by miners depending on the fee offered.
+ """
+ @spec get_average_gas_price(pos_integer(), pos_integer(), pos_integer(), pos_integer()) ::
+ {{:error, any} | {:ok, %{slow: gas_price, average: gas_price, fast: gas_price}},
+ [
+ %{
+ block_number: non_neg_integer(),
+ slow_gas_price: nil | Decimal.t(),
+ fast_gas_price: nil | Decimal.t(),
+ average_gas_price: nil | Decimal.t(),
+ slow_priority_fee_per_gas: nil | Decimal.t(),
+ average_priority_fee_per_gas: nil | Decimal.t(),
+ fast_priority_fee_per_gas: nil | Decimal.t(),
+ slow_time: nil | Decimal.t(),
+ average_time: nil | Decimal.t(),
+ fast_time: nil | Decimal.t()
+ }
+ ]}
+ when gas_price: nil | %{price: float(), time: float(), fiat_price: Decimal.t()}
def get_average_gas_price(num_of_blocks, safelow_percentile, average_percentile, fast_percentile) do
- latest_gas_price_query =
- from(
- block in Block,
- left_join: transaction in assoc(block, :transactions),
- where: block.consensus == true,
- where: transaction.status == ^1,
- where: transaction.gas_price > ^0,
- group_by: block.number,
- order_by: [desc: block.number],
- select: min(transaction.gas_price),
- limit: ^num_of_blocks
- )
-
- latest_gas_prices =
- latest_gas_price_query
- |> Repo.all(timeout: :infinity)
-
- latest_ordered_gas_prices =
- latest_gas_prices
- |> Enum.map(fn %Wei{value: gas_price} -> Decimal.to_integer(gas_price) end)
-
- safelow_gas_price = gas_price_percentile_to_gwei(latest_ordered_gas_prices, safelow_percentile)
- average_gas_price = gas_price_percentile_to_gwei(latest_ordered_gas_prices, average_percentile)
- fast_gas_price = gas_price_percentile_to_gwei(latest_ordered_gas_prices, fast_percentile)
-
- gas_prices = %{
- "slow" => safelow_gas_price,
- "average" => average_gas_price,
- "fast" => fast_gas_price
- }
+ safelow_percentile_fraction = safelow_percentile / 100
+ average_percentile_fraction = average_percentile / 100
+ fast_percentile_fraction = fast_percentile / 100
- {:ok, gas_prices}
+ acc = get_gas_prices_acc()
+
+ from_block =
+ case acc do
+ [%{block_number: from_block} | _] -> from_block
+ _ -> -1
+ end
+
+ average_block_time =
+ case AverageBlockTime.average_block_time() do
+ {:error, _} -> nil
+ average_block_time -> average_block_time |> Duration.to_milliseconds()
+ end
+
+ fee_query =
+ if DenormalizationHelper.denormalization_finished?() do
+ from(
+ transaction in Transaction,
+ where: transaction.block_consensus == true,
+ where: transaction.status == ^1,
+ where: transaction.gas_price > ^0,
+ where: transaction.block_number > ^from_block,
+ group_by: transaction.block_number,
+ order_by: [desc: transaction.block_number],
+ select: %{
+ block_number: transaction.block_number,
+ slow_gas_price:
+ fragment(
+ "percentile_disc(? :: real) within group ( order by ? )",
+ ^safelow_percentile_fraction,
+ transaction.gas_price
+ ),
+ average_gas_price:
+ fragment(
+ "percentile_disc(? :: real) within group ( order by ? )",
+ ^average_percentile_fraction,
+ transaction.gas_price
+ ),
+ fast_gas_price:
+ fragment(
+ "percentile_disc(? :: real) within group ( order by ? )",
+ ^fast_percentile_fraction,
+ transaction.gas_price
+ ),
+ slow_priority_fee_per_gas:
+ fragment(
+ "percentile_disc(? :: real) within group ( order by ? )",
+ ^safelow_percentile_fraction,
+ transaction.max_priority_fee_per_gas
+ ),
+ average_priority_fee_per_gas:
+ fragment(
+ "percentile_disc(? :: real) within group ( order by ? )",
+ ^average_percentile_fraction,
+ transaction.max_priority_fee_per_gas
+ ),
+ fast_priority_fee_per_gas:
+ fragment(
+ "percentile_disc(? :: real) within group ( order by ? )",
+ ^fast_percentile_fraction,
+ transaction.max_priority_fee_per_gas
+ ),
+ slow_time:
+ fragment(
+ "percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )",
+ ^safelow_percentile_fraction,
+ transaction.block_timestamp - transaction.earliest_processing_start,
+ ^average_block_time
+ ),
+ average_time:
+ fragment(
+ "percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )",
+ ^average_percentile_fraction,
+ transaction.block_timestamp - transaction.earliest_processing_start,
+ ^average_block_time
+ ),
+ fast_time:
+ fragment(
+ "percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )",
+ ^fast_percentile_fraction,
+ transaction.block_timestamp - transaction.earliest_processing_start,
+ ^average_block_time
+ )
+ },
+ limit: ^num_of_blocks
+ )
+ else
+ from(
+ block in Block,
+ left_join: transaction in assoc(block, :transactions),
+ where: block.consensus == true,
+ where: transaction.status == ^1,
+ where: transaction.gas_price > ^0,
+ where: transaction.block_number > ^from_block,
+ group_by: transaction.block_number,
+ order_by: [desc: transaction.block_number],
+ select: %{
+ block_number: transaction.block_number,
+ slow_gas_price:
+ fragment(
+ "percentile_disc(? :: real) within group ( order by ? )",
+ ^safelow_percentile_fraction,
+ transaction.gas_price
+ ),
+ average_gas_price:
+ fragment(
+ "percentile_disc(? :: real) within group ( order by ? )",
+ ^average_percentile_fraction,
+ transaction.gas_price
+ ),
+ fast_gas_price:
+ fragment(
+ "percentile_disc(? :: real) within group ( order by ? )",
+ ^fast_percentile_fraction,
+ transaction.gas_price
+ ),
+ slow_priority_fee_per_gas:
+ fragment(
+ "percentile_disc(? :: real) within group ( order by ? )",
+ ^safelow_percentile_fraction,
+ transaction.max_priority_fee_per_gas
+ ),
+ average_priority_fee_per_gas:
+ fragment(
+ "percentile_disc(? :: real) within group ( order by ? )",
+ ^average_percentile_fraction,
+ transaction.max_priority_fee_per_gas
+ ),
+ fast_priority_fee_per_gas:
+ fragment(
+ "percentile_disc(? :: real) within group ( order by ? )",
+ ^fast_percentile_fraction,
+ transaction.max_priority_fee_per_gas
+ ),
+ slow_time:
+ fragment(
+ "percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )",
+ ^safelow_percentile_fraction,
+ block.timestamp - transaction.earliest_processing_start,
+ ^average_block_time
+ ),
+ average_time:
+ fragment(
+ "percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )",
+ ^average_percentile_fraction,
+ block.timestamp - transaction.earliest_processing_start,
+ ^average_block_time
+ ),
+ fast_time:
+ fragment(
+ "percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )",
+ ^fast_percentile_fraction,
+ block.timestamp - transaction.earliest_processing_start,
+ ^average_block_time
+ )
+ },
+ limit: ^num_of_blocks
+ )
+ end
+
+ new_acc = fee_query |> Repo.all(timeout: :infinity) |> merge_gas_prices(acc, num_of_blocks)
+
+ gas_prices = new_acc |> process_fee_data_from_db()
+
+ {{:ok, gas_prices}, new_acc}
catch
error ->
- {:error, error}
+ Logger.error("Failed to get gas prices: #{inspect(error)}")
+ {{:error, error}, get_gas_prices_acc()}
+ end
+
+ defp merge_gas_prices(new, acc, acc_size), do: Enum.take(new ++ acc, acc_size)
+
+ defp process_fee_data_from_db([]) do
+ %{
+ slow: nil,
+ average: nil,
+ fast: nil
+ }
end
+ defp process_fee_data_from_db(fees) do
+ %{
+ slow_gas_price: slow_gas_price,
+ average_gas_price: average_gas_price,
+ fast_gas_price: fast_gas_price,
+ slow_priority_fee_per_gas: slow_priority_fee_per_gas,
+ average_priority_fee_per_gas: average_priority_fee_per_gas,
+ fast_priority_fee_per_gas: fast_priority_fee_per_gas,
+ slow_time: slow_time,
+ average_time: average_time,
+ fast_time: fast_time
+ } = merge_fees(fees)
+
+ json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
+
+ {slow_fee, average_fee, fast_fee} =
+ case nil not in [slow_priority_fee_per_gas, average_priority_fee_per_gas, fast_priority_fee_per_gas] &&
+ EthereumJSONRPC.fetch_block_by_tag("pending", json_rpc_named_arguments) do
+ {:ok, %Blocks{blocks_params: [%{base_fee_per_gas: base_fee}]}} when not is_nil(base_fee) ->
+ base_fee_wei = base_fee |> Decimal.new() |> Wei.from(:wei)
+
+ {
+ priority_with_base_fee(slow_priority_fee_per_gas, base_fee_wei),
+ priority_with_base_fee(average_priority_fee_per_gas, base_fee_wei),
+ priority_with_base_fee(fast_priority_fee_per_gas, base_fee_wei)
+ }
+
+ _ ->
+ {gas_price(slow_gas_price), gas_price(average_gas_price), gas_price(fast_gas_price)}
+ end
+
+ exchange_rate_from_db = Market.get_coin_exchange_rate()
+
+ %{
+ slow: compose_gas_price(slow_fee, slow_time, exchange_rate_from_db),
+ average: compose_gas_price(average_fee, average_time, exchange_rate_from_db),
+ fast: compose_gas_price(fast_fee, fast_time, exchange_rate_from_db)
+ }
+ end
+
+ defp merge_fees(fees_from_db) do
+ fees_from_db
+ |> Stream.map(&Map.delete(&1, :block_number))
+ |> Enum.reduce(
+ &Map.merge(&1, &2, fn
+ _, nil, nil -> nil
+ _, val, nil -> [val]
+ _, nil, acc -> if is_list(acc), do: acc, else: [acc]
+ _, val, acc -> if is_list(acc), do: [val | acc], else: [val, acc]
+ end)
+ )
+ |> Map.new(fn
+ {key, nil} ->
+ {key, nil}
+
+ {key, value} ->
+ value = if is_list(value), do: value, else: [value]
+ count = Enum.count(value)
+ {key, value |> Enum.reduce(Decimal.new(0), &Decimal.add/2) |> Decimal.div(count)}
+ end)
+ end
+
+ defp compose_gas_price(fee, time, exchange_rate_from_db) do
+ %{
+ price: fee |> format_wei(),
+ time: time && time |> Decimal.to_float(),
+ fiat_price: fiat_fee(fee, exchange_rate_from_db)
+ }
+ end
+
+ defp fiat_fee(fee, exchange_rate) do
+ exchange_rate.usd_value &&
+ fee
+ |> Wei.to(:ether)
+ |> Decimal.mult(exchange_rate.usd_value)
+ |> Decimal.mult(simple_transaction_gas())
+ |> Decimal.round(2)
+ end
+
+ defp priority_with_base_fee(priority, base_fee) do
+ priority |> Wei.from(:wei) |> Wei.sum(base_fee)
+ end
+
+ defp gas_price(value) do
+ value |> Wei.from(:wei)
+ end
+
+ defp format_wei(wei), do: wei |> Wei.to(:gwei) |> Decimal.to_float() |> Float.ceil(2)
+
+ defp global_ttl, do: Application.get_env(:explorer, __MODULE__)[:global_ttl]
+
+ defp simple_transaction_gas, do: Application.get_env(:explorer, __MODULE__)[:simple_transaction_gas]
+
defp num_of_blocks, do: Application.get_env(:explorer, __MODULE__)[:num_of_blocks]
defp safelow, do: Application.get_env(:explorer, __MODULE__)[:safelow_percentile]
@@ -76,7 +365,7 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do
# See next `handle_fallback` definition
get_async_task()
- {:return, nil}
+ {:return, get_old_gas_prices()}
end
defp handle_fallback(:async_task) do
@@ -85,12 +374,15 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do
{:ok, task} =
Task.start(fn ->
try do
- result = get_average_gas_price(num_of_blocks(), safelow(), average(), fast())
+ {result, acc} = get_average_gas_price(num_of_blocks(), safelow(), average(), fast())
- set_all(result)
+ set_gas_prices_acc(acc)
+ set_gas_prices(%ConCache.Item{ttl: global_ttl(), value: result})
+ set_old_updated_at(get_updated_at())
+ set_updated_at(DateTime.utc_now())
rescue
e ->
- Logger.debug([
+ Logger.error([
"Couldn't update gas used gas_prices",
Exception.format(:error, e, __STACKTRACE__)
])
@@ -102,43 +394,18 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do
{:update, task}
end
- defp gas_price_percentile_to_gwei(gas_prices, percentile) do
- gas_price_wei = percentile(gas_prices, percentile)
-
- if gas_price_wei do
- gas_price_gwei = Wei.to(%Wei{value: Decimal.from_float(gas_price_wei)}, :gwei)
-
- gas_price_gwei_float = gas_price_gwei |> Decimal.to_float()
-
- if gas_price_gwei_float > 0.01 do
- gas_price_gwei_float
- |> Float.ceil(2)
- else
- gas_price_gwei_float
- end
- else
- nil
- end
+ defp handle_fallback(:gas_prices_acc) do
+ {:return, []}
end
- @spec percentile(list, number) :: number | nil
- defp percentile([], _), do: nil
- defp percentile([x], _), do: x
- defp percentile(list, 0), do: Enum.min(list)
- defp percentile(list, 100), do: Enum.max(list)
-
- defp percentile(list, n) when is_list(list) and is_number(n) do
- s = Enum.sort(list)
- r = n / 100.0 * (length(list) - 1)
- f = :erlang.trunc(r)
- lower = Enum.at(s, f)
- upper = Enum.at(s, f + 1)
- lower + (upper - lower) * (r - f)
- end
+ defp handle_fallback(_), do: {:return, nil}
# By setting this as a `callback` an async task will be started each time the
# `gas_prices` expires (unless there is one already running)
- defp async_task_on_deletion({:delete, _, :gas_prices}), do: get_async_task()
+ defp async_task_on_deletion({:delete, _, :gas_prices}) do
+ set_old_gas_prices(get_gas_prices())
+ get_async_task()
+ end
defp async_task_on_deletion(_data), do: nil
end
diff --git a/apps/explorer/lib/explorer/chain/cache/pending_block_operation.ex b/apps/explorer/lib/explorer/chain/cache/pending_block_operation.ex
new file mode 100644
index 000000000000..dc0f01e59cbe
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/cache/pending_block_operation.ex
@@ -0,0 +1,73 @@
+defmodule Explorer.Chain.Cache.PendingBlockOperation do
+ @moduledoc """
+ Cache for estimated `pending_block_operations` count.
+ """
+
+ use Explorer.Chain.MapCache,
+ name: :pending_block_operations_count,
+ key: :count,
+ key: :async_task,
+ global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl],
+ ttl_check_interval: :timer.seconds(1),
+ callback: &async_task_on_deletion(&1)
+
+ require Logger
+
+ alias Explorer.Chain.Cache.Helper
+ alias Explorer.Chain.PendingBlockOperation
+ alias Explorer.Repo
+
+ @doc """
+ Estimated count of `t:Explorer.Chain.PendingBlockOperation.t/0`.
+
+ """
+ @spec estimated_count() :: non_neg_integer()
+ def estimated_count do
+ cached_value = __MODULE__.get_count()
+
+ if is_nil(cached_value) do
+ count = Helper.estimated_count_from("pending_block_operations")
+
+ max(count, 0)
+ else
+ cached_value
+ end
+ end
+
+ defp handle_fallback(:count) do
+ # This will get the task PID if one exists and launch a new task if not
+ # See next `handle_fallback` definition
+ get_async_task()
+
+ {:return, nil}
+ end
+
+ defp handle_fallback(:async_task) do
+ # If this gets called it means an async task was requested, but none exists
+ # so a new one needs to be launched
+ {:ok, task} =
+ Task.start(fn ->
+ try do
+ result = Repo.aggregate(PendingBlockOperation, :count, timeout: :infinity)
+
+ set_count(result)
+ rescue
+ e ->
+ Logger.debug([
+ "Couldn't update pending_block_operations count: ",
+ Exception.format(:error, e, __STACKTRACE__)
+ ])
+ end
+
+ set_async_task(nil)
+ end)
+
+ {:update, task}
+ end
+
+ # By setting this as a `callback` an async task will be started each time the
+ # `count` expires (unless there is one already running)
+ defp async_task_on_deletion({:delete, _, :count}), do: get_async_task()
+
+ defp async_task_on_deletion(_data), do: nil
+end
diff --git a/apps/explorer/lib/explorer/chain/cache/rootstock_locked_btc.ex b/apps/explorer/lib/explorer/chain/cache/rootstock_locked_btc.ex
new file mode 100644
index 000000000000..e4bf1519787b
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/cache/rootstock_locked_btc.ex
@@ -0,0 +1,31 @@
+defmodule Explorer.Chain.Cache.RootstockLockedBTC do
+ @moduledoc """
+ Caches the number of BTC locked in 2WP on Rootstock chain.
+ """
+
+ require Logger
+ alias Explorer.Chain
+ alias Explorer.Chain.{Address, Wei}
+
+ use Explorer.Chain.MapCache,
+ name: :locked_rsk,
+ key: :locked_value,
+ global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl],
+ ttl_check_interval: :timer.seconds(1)
+
+ defp handle_fallback(:locked_value) do
+ rootstock_bridge_address_str = Application.get_env(:explorer, Explorer.Chain.Transaction)[:rootstock_bridge_address]
+ rootstock_locking_cap = Application.get_env(:explorer, __MODULE__)[:locking_cap] |> Decimal.new()
+
+ with {:ok, rootstock_bridge_address_hash} <- Chain.string_to_address_hash(rootstock_bridge_address_str),
+ {:ok, %Address{fetched_coin_balance: balance}} when not is_nil(balance) <-
+ Chain.hash_to_address(rootstock_bridge_address_hash) do
+ {:update, rootstock_locking_cap |> Wei.from(:ether) |> Wei.sub(balance)}
+ else
+ _ ->
+ {:return, nil}
+ end
+ end
+
+ defp handle_fallback(_key), do: {:return, nil}
+end
diff --git a/apps/explorer/lib/explorer/chain/cache/state_changes.ex b/apps/explorer/lib/explorer/chain/cache/state_changes.ex
new file mode 100644
index 000000000000..f11f48efedcb
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/cache/state_changes.ex
@@ -0,0 +1,32 @@
+defmodule Explorer.Chain.Cache.StateChanges do
+ @moduledoc """
+ Caches the transaction state changes for pagination
+ """
+
+ alias Explorer.Chain.{Hash, OrderedCache}
+ alias Explorer.Chain.Transaction.StateChange
+
+ defstruct [:transaction_hash, :state_changes]
+
+ @type t :: %__MODULE__{
+ transaction_hash: Hash.t(),
+ state_changes: [StateChange.t()]
+ }
+
+ use OrderedCache,
+ name: :state_changes,
+ max_size: 10
+
+ @type element :: t()
+
+ @type id :: Hash.t()
+
+ def element_to_id(%__MODULE__{transaction_hash: tx_hash}) do
+ tx_hash
+ end
+
+ # in order to always keep just requested changes
+ def prevails?(a, b) do
+ a == b
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/cache/withdrawals_sum.ex b/apps/explorer/lib/explorer/chain/cache/withdrawals_sum.ex
index b19a0a328274..cac2cf3a0384 100644
--- a/apps/explorer/lib/explorer/chain/cache/withdrawals_sum.ex
+++ b/apps/explorer/lib/explorer/chain/cache/withdrawals_sum.ex
@@ -12,6 +12,7 @@ defmodule Explorer.Chain.Cache.WithdrawalsSum do
use GenServer
alias Explorer.Chain
+ alias Explorer.Chain.Wei
@counter_type "withdrawals_sum"
@@ -68,7 +69,7 @@ defmodule Explorer.Chain.Cache.WithdrawalsSum do
params = %{
counter_type: @counter_type,
- value: withdrawals_sum
+ value: (withdrawals_sum && Wei.to(withdrawals_sum, :wei)) || 0
}
Chain.upsert_last_fetched_counter(params)
diff --git a/apps/explorer/lib/explorer/chain/contract_method.ex b/apps/explorer/lib/explorer/chain/contract_method.ex
index 05a82406c95d..dd1cf0a80923 100644
--- a/apps/explorer/lib/explorer/chain/contract_method.ex
+++ b/apps/explorer/lib/explorer/chain/contract_method.ex
@@ -5,6 +5,7 @@ defmodule Explorer.Chain.ContractMethod do
require Logger
+ import Ecto.Query, only: [from: 2]
use Explorer.Schema
alias Explorer.Chain.{Hash, MethodIdentifier, SmartContract}
@@ -69,6 +70,18 @@ defmodule Explorer.Chain.ContractMethod do
end
end
+ @doc """
+ Finds limited number of contract methods by selector id
+ """
+ @spec find_contract_method_query(binary(), integer()) :: Ecto.Query.t()
+ def find_contract_method_query(method_id, limit) do
+ from(
+ contract_method in __MODULE__,
+ where: contract_method.identifier == ^method_id,
+ limit: ^limit
+ )
+ end
+
defp abi_element_to_contract_method(element) do
case ABI.parse_specification([element], include_events?: true) do
[selector] ->
diff --git a/apps/explorer/lib/explorer/chain/csv_export/address_internal_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_internal_transaction_csv_exporter.ex
index a3872ae8ef66..f54ae2b36d13 100644
--- a/apps/explorer/lib/explorer/chain/csv_export/address_internal_transaction_csv_exporter.ex
+++ b/apps/explorer/lib/explorer/chain/csv_export/address_internal_transaction_csv_exporter.ex
@@ -4,45 +4,42 @@ defmodule Explorer.Chain.CSVExport.AddressInternalTransactionCsvExporter do
"""
alias Explorer.{Chain, PagingOptions}
- alias Explorer.Chain.{Address, InternalTransaction, Wei}
+ alias Explorer.Chain.{Address, Hash, Wei}
alias Explorer.Chain.CSVExport.Helper
- @paging_options %PagingOptions{page_size: Helper.page_size() + 1}
+ @paging_options %PagingOptions{page_size: Helper.limit()}
- @spec export(Address.t(), String.t(), String.t()) :: Enumerable.t()
- def export(address, from_period, to_period) do
+ @spec export(Hash.Address.t(), String.t(), String.t(), String.t() | nil, String.t() | nil) :: Enumerable.t()
+ def export(address_hash, from_period, to_period, filter_type \\ nil, filter_value \\ nil) do
{from_block, to_block} = Helper.block_from_period(from_period, to_period)
- res =
- address.hash
- |> fetch_all_internal_transactions(from_block, to_block, @paging_options)
- |> Enum.sort_by(&{&1.block_number, &1.index, &1.transaction_index}, :desc)
- |> to_csv_format()
- |> Helper.dump_to_stream()
-
- res
+ address_hash
+ |> fetch_all_internal_transactions(from_block, to_block, filter_type, filter_value, @paging_options)
+ |> Enum.sort_by(&{&1.block_number, &1.index, &1.transaction_index}, :desc)
+ |> to_csv_format()
+ |> Helper.dump_to_stream()
end
- defp fetch_all_internal_transactions(address_hash, from_block, to_block, paging_options, acc \\ []) do
+ # sobelow_skip ["DOS.StringToAtom"]
+ defp fetch_all_internal_transactions(
+ address_hash,
+ from_block,
+ to_block,
+ filter_type,
+ filter_value,
+ paging_options
+ ) do
options =
[]
|> Keyword.put(:paging_options, paging_options)
|> Keyword.put(:from_block, from_block)
|> Keyword.put(:to_block, to_block)
+ |> (&if(Helper.is_valid_filter?(filter_type, filter_value, "internal_transactions"),
+ do: &1 |> Keyword.put(:direction, String.to_atom(filter_value)),
+ else: &1
+ )).()
- internal_transactions = Chain.address_to_internal_transactions(address_hash, options)
-
- new_acc = internal_transactions ++ acc
-
- case Enum.split(internal_transactions, Helper.page_size()) do
- {_internal_transactions,
- [%InternalTransaction{block_number: block_number, transaction_index: transaction_index, index: index}]} ->
- new_paging_options = %{@paging_options | key: {block_number, transaction_index, index}}
- fetch_all_internal_transactions(address_hash, from_block, to_block, new_paging_options, new_acc)
-
- {_, []} ->
- new_acc
- end
+ Chain.address_to_internal_transactions(address_hash, options)
end
defp to_csv_format(internal_transactions) do
@@ -77,10 +74,10 @@ defmodule Explorer.Chain.CSVExport.AddressInternalTransactionCsvExporter do
internal_transaction.block_hash,
internal_transaction.block_index,
internal_transaction.transaction_index,
- internal_transaction.transaction.block.timestamp,
- to_string(internal_transaction.from_address_hash),
- to_string(internal_transaction.to_address_hash),
- to_string(internal_transaction.created_contract_address_hash),
+ internal_transaction.block.timestamp,
+ Address.checksum(internal_transaction.from_address_hash),
+ Address.checksum(internal_transaction.to_address_hash),
+ Address.checksum(internal_transaction.created_contract_address_hash),
internal_transaction.type,
internal_transaction.call_type,
internal_transaction.gas,
diff --git a/apps/explorer/lib/explorer/chain/csv_export/address_log_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_log_csv_exporter.ex
index 55b339d6cb8c..7432cb8a274a 100644
--- a/apps/explorer/lib/explorer/chain/csv_export/address_log_csv_exporter.ex
+++ b/apps/explorer/lib/explorer/chain/csv_export/address_log_csv_exporter.ex
@@ -4,40 +4,30 @@ defmodule Explorer.Chain.CSVExport.AddressLogCsvExporter do
"""
alias Explorer.{Chain, PagingOptions}
- alias Explorer.Chain.{Address, Log, Transaction}
+ alias Explorer.Chain.{Address, Hash}
alias Explorer.Chain.CSVExport.Helper
- @paging_options %PagingOptions{page_size: Helper.page_size() + 1}
+ @paging_options %PagingOptions{page_size: Helper.limit()}
- @spec export(Address.t(), String.t(), String.t()) :: Enumerable.t()
- def export(address, from_period, to_period) do
+ @spec export(Hash.Address.t(), String.t(), String.t(), String.t() | nil, String.t() | nil) :: Enumerable.t()
+ def export(address_hash, from_period, to_period, _filter_type \\ nil, filter_value \\ nil) do
{from_block, to_block} = Helper.block_from_period(from_period, to_period)
- address.hash
- |> fetch_all_logs(from_block, to_block, @paging_options)
+ address_hash
+ |> fetch_all_logs(from_block, to_block, filter_value, @paging_options)
|> to_csv_format()
|> Helper.dump_to_stream()
end
- defp fetch_all_logs(address_hash, from_block, to_block, paging_options, acc \\ []) do
+ defp fetch_all_logs(address_hash, from_block, to_block, filter_value, paging_options) do
options =
[]
|> Keyword.put(:paging_options, paging_options)
|> Keyword.put(:from_block, from_block)
|> Keyword.put(:to_block, to_block)
+ |> Keyword.put(:topic, filter_value)
- logs = Chain.address_to_logs(address_hash, options)
-
- new_acc = logs ++ acc
-
- case Enum.split(logs, Helper.page_size()) do
- {_logs, [%Log{block_number: block_number, transaction: %Transaction{index: transaction_index}, index: index}]} ->
- new_paging_options = %{@paging_options | key: {block_number, transaction_index, index}}
- fetch_all_logs(address_hash, from_block, to_block, new_paging_options, new_acc)
-
- {_, []} ->
- new_acc
- end
+ Chain.address_to_logs(address_hash, true, options)
end
defp to_csv_format(logs) do
@@ -62,7 +52,7 @@ defmodule Explorer.Chain.CSVExport.AddressLogCsvExporter do
log.index,
log.block_number,
log.block_hash,
- to_string(log.address_hash),
+ Address.checksum(log.address_hash),
to_string(log.data),
to_string(log.first_topic),
to_string(log.second_topic),
diff --git a/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex
index d2e595d701e6..5a44e272a726 100644
--- a/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex
+++ b/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex
@@ -3,44 +3,50 @@ defmodule Explorer.Chain.CSVExport.AddressTokenTransferCsvExporter do
Exports token transfers to a csv file.
"""
- alias Explorer.{Chain, PagingOptions}
- alias Explorer.Chain.{Address, TokenTransfer}
+ import Ecto.Query,
+ only: [
+ limit: 2,
+ preload: 2,
+ order_by: 3,
+ where: 3
+ ]
+
+ alias Explorer.{Chain, PagingOptions, Repo}
+ alias Explorer.Chain.{Address, DenormalizationHelper, Hash, TokenTransfer, Transaction}
alias Explorer.Chain.CSVExport.Helper
- @paging_options %PagingOptions{page_size: Helper.page_size() + 1, asc_order: true}
+ @paging_options %PagingOptions{page_size: Helper.limit(), asc_order: true}
- @spec export(Address.t(), String.t(), String.t()) :: Enumerable.t()
- def export(address, from_period, to_period) do
+ @spec export(Hash.Address.t(), String.t(), String.t(), String.t() | nil, String.t() | nil) :: Enumerable.t()
+ def export(address_hash, from_period, to_period, filter_type \\ nil, filter_value \\ nil) do
{from_block, to_block} = Helper.block_from_period(from_period, to_period)
- address.hash
- |> fetch_all_token_transfers(from_block, to_block, @paging_options)
- |> to_csv_format(address)
+ address_hash
+ |> fetch_all_token_transfers(from_block, to_block, filter_type, filter_value, @paging_options)
+ |> to_csv_format(address_hash)
|> Helper.dump_to_stream()
end
- def fetch_all_token_transfers(address_hash, from_block, to_block, paging_options, acc \\ []) do
+ def fetch_all_token_transfers(
+ address_hash,
+ from_block,
+ to_block,
+ filter_type,
+ filter_value,
+ paging_options
+ ) do
options =
[]
|> Keyword.put(:paging_options, paging_options)
|> Keyword.put(:from_block, from_block)
|> Keyword.put(:to_block, to_block)
+ |> Keyword.put(:filter_type, filter_type)
+ |> Keyword.put(:filter_value, filter_value)
- token_transfers = Chain.address_hash_to_token_transfers_including_contract(address_hash, options)
-
- new_acc = acc ++ token_transfers
-
- case Enum.split(token_transfers, Helper.page_size()) do
- {_token_transfers, [%TokenTransfer{block_number: block_number, log_index: log_index}]} ->
- new_paging_options = %{@paging_options | key: {block_number, log_index}}
- fetch_all_token_transfers(address_hash, from_block, to_block, new_paging_options, new_acc)
-
- {_, []} ->
- new_acc
- end
+ address_hash_to_token_transfers_including_contract(address_hash, options)
end
- defp to_csv_format(token_transfers, address) do
+ defp to_csv_format(token_transfers, address_hash) do
row_names = [
"TxHash",
"BlockNumber",
@@ -62,11 +68,11 @@ defmodule Explorer.Chain.CSVExport.AddressTokenTransferCsvExporter do
[
to_string(token_transfer.transaction_hash),
token_transfer.transaction.block_number,
- token_transfer.transaction.block.timestamp,
- token_transfer.from_address_hash |> to_string() |> String.downcase(),
- token_transfer.to_address_hash |> to_string() |> String.downcase(),
- token_transfer.token_contract_address_hash |> to_string() |> String.downcase(),
- type(token_transfer, address.hash),
+ Transaction.block_timestamp(token_transfer.transaction),
+ Address.checksum(token_transfer.from_address_hash),
+ Address.checksum(token_transfer.to_address_hash),
+ Address.checksum(token_transfer.token_contract_address_hash),
+ type(token_transfer, address_hash),
token_transfer.token.symbol,
token_transfer.amount,
fee(token_transfer.transaction),
@@ -92,4 +98,74 @@ defmodule Explorer.Chain.CSVExport.AddressTokenTransferCsvExporter do
{:maximum, value} -> "Max of #{value}"
end
end
+
+ @doc """
+ address_hash_to_token_transfers_including_contract/2 function returns token transfers on address (to/from/contract).
+ It is used by CSV export of token transfers button.
+ """
+ @spec address_hash_to_token_transfers_including_contract(Hash.Address.t(), Keyword.t()) :: [TokenTransfer.t()]
+ def address_hash_to_token_transfers_including_contract(address_hash, options \\ []) do
+ paging_options = Keyword.get(options, :paging_options, Helper.default_paging_options())
+ from_block = Keyword.get(options, :from_block)
+ to_block = Keyword.get(options, :to_block)
+ filter_type = Keyword.get(options, :filter_type)
+ filter_value = Keyword.get(options, :filter_value)
+
+ query =
+ from_block
+ |> query_address_hash_to_token_transfers_including_contract(to_block, address_hash, filter_type, filter_value)
+ |> order_by([token_transfer], asc: token_transfer.block_number, asc: token_transfer.log_index)
+
+ query
+ |> handle_token_transfer_paging_options(paging_options)
+ |> preload(^DenormalizationHelper.extend_transaction_preload([:transaction]))
+ |> preload(:token)
+ |> Repo.all()
+ end
+
+ defp query_address_hash_to_token_transfers_including_contract(nil, to_block, address_hash, filter_type, filter_value)
+ when not is_nil(to_block) do
+ TokenTransfer
+ |> Helper.where_address_hash(address_hash, filter_type, filter_value)
+ |> where([token_transfer], token_transfer.block_number <= ^to_block)
+ end
+
+ defp query_address_hash_to_token_transfers_including_contract(
+ from_block,
+ nil,
+ address_hash,
+ filter_type,
+ filter_value
+ )
+ when not is_nil(from_block) do
+ TokenTransfer
+ |> Helper.where_address_hash(address_hash, filter_type, filter_value)
+ |> where([token_transfer], token_transfer.block_number >= ^from_block)
+ end
+
+ defp query_address_hash_to_token_transfers_including_contract(
+ from_block,
+ to_block,
+ address_hash,
+ filter_type,
+ filter_value
+ )
+ when not is_nil(from_block) and not is_nil(to_block) do
+ TokenTransfer
+ |> Helper.where_address_hash(address_hash, filter_type, filter_value)
+ |> where([token_transfer], token_transfer.block_number >= ^from_block and token_transfer.block_number <= ^to_block)
+ end
+
+ defp query_address_hash_to_token_transfers_including_contract(_, _, address_hash, filter_type, filter_value) do
+ TokenTransfer
+ |> Helper.where_address_hash(address_hash, filter_type, filter_value)
+ end
+
+ defp handle_token_transfer_paging_options(query, nil), do: query
+
+ defp handle_token_transfer_paging_options(query, paging_options) do
+ query
+ |> TokenTransfer.page_token_transfer(paging_options)
+ |> limit(^paging_options.page_size)
+ end
end
diff --git a/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex
index c76815dc70b6..600d53d63aa3 100644
--- a/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex
+++ b/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex
@@ -10,57 +10,39 @@ defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do
alias Explorer.{Chain, Market, PagingOptions, Repo}
alias Explorer.Market.MarketHistory
- alias Explorer.Chain.{Address, Transaction, Wei}
+ alias Explorer.Chain.{Address, DenormalizationHelper, Hash, Transaction, Wei}
alias Explorer.Chain.CSVExport.Helper
- alias Explorer.ExchangeRates.Token
-
- @necessity_by_association [
- necessity_by_association: %{
- [created_contract_address: :names] => :optional,
- [from_address: :names] => :optional,
- [to_address: :names] => :optional,
- [token_transfers: :token] => :optional,
- [token_transfers: :to_address] => :optional,
- [token_transfers: :from_address] => :optional,
- [token_transfers: :token_contract_address] => :optional,
- :block => :required
- }
- ]
-
- @paging_options %PagingOptions{page_size: Helper.page_size() + 1}
-
- @spec export(Address.t(), String.t(), String.t()) :: Enumerable.t()
- def export(address, from_period, to_period) do
+
+ @paging_options %PagingOptions{page_size: Helper.limit()}
+
+ @spec export(Hash.Address.t(), String.t(), String.t(), String.t() | nil, String.t() | nil) :: Enumerable.t()
+ def export(address_hash, from_period, to_period, filter_type \\ nil, filter_value \\ nil) do
{from_block, to_block} = Helper.block_from_period(from_period, to_period)
- exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null()
+ exchange_rate = Market.get_coin_exchange_rate()
- address.hash
- |> fetch_all_transactions(from_block, to_block, @paging_options)
- |> to_csv_format(address, exchange_rate)
+ address_hash
+ |> fetch_transactions(from_block, to_block, filter_type, filter_value, @paging_options)
+ |> to_csv_format(address_hash, exchange_rate)
|> Helper.dump_to_stream()
end
- def fetch_all_transactions(address_hash, from_block, to_block, paging_options, acc \\ []) do
+ # sobelow_skip ["DOS.StringToAtom"]
+ def fetch_transactions(address_hash, from_block, to_block, filter_type, filter_value, paging_options) do
options =
- @necessity_by_association
+ []
+ |> DenormalizationHelper.extend_block_necessity(:required)
|> Keyword.put(:paging_options, paging_options)
|> Keyword.put(:from_block, from_block)
|> Keyword.put(:to_block, to_block)
+ |> (&if(Helper.is_valid_filter?(filter_type, filter_value, "transactions"),
+ do: &1 |> Keyword.put(:direction, String.to_atom(filter_value)),
+ else: &1
+ )).()
- transactions = Chain.address_to_transactions_without_rewards(address_hash, options)
- new_acc = transactions ++ acc
-
- case Enum.split(transactions, Helper.page_size()) do
- {_transactions, [%Transaction{block_number: block_number, index: index}]} ->
- new_paging_options = %{@paging_options | key: {block_number, index}}
- fetch_all_transactions(address_hash, from_block, to_block, new_paging_options, new_acc)
-
- {_, []} ->
- new_acc
- end
+ Transaction.address_to_transactions_without_rewards(address_hash, options)
end
- defp to_csv_format(transactions, address, exchange_rate) do
+ defp to_csv_format(transactions, address_hash, exchange_rate) do
row_names = [
"TxHash",
"BlockNumber",
@@ -78,19 +60,30 @@ defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do
"TxDateClosingPrice"
]
+ date_to_prices =
+ Enum.reduce(transactions, %{}, fn tx, acc ->
+ date = tx |> Transaction.block_timestamp() |> DateTime.to_date()
+
+ if Map.has_key?(acc, date) do
+ acc
+ else
+ Map.put(acc, date, price_at_date(date))
+ end
+ end)
+
transaction_lists =
transactions
|> Stream.map(fn transaction ->
- {opening_price, closing_price} = price_at_date(transaction.block.timestamp)
+ {opening_price, closing_price} = date_to_prices[DateTime.to_date(Transaction.block_timestamp(transaction))]
[
to_string(transaction.hash),
transaction.block_number,
- transaction.block.timestamp,
- to_string(transaction.from_address),
- to_string(transaction.to_address),
- to_string(transaction.created_contract_address),
- type(transaction, address.hash),
+ Transaction.block_timestamp(transaction),
+ Address.checksum(transaction.from_address_hash),
+ Address.checksum(transaction.to_address_hash),
+ Address.checksum(transaction.created_contract_address_hash),
+ type(transaction, address_hash),
Wei.to(transaction.value, :wei),
fee(transaction),
transaction.status,
@@ -119,9 +112,7 @@ defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do
end
end
- defp price_at_date(datetime) do
- date = DateTime.to_date(datetime)
-
+ defp price_at_date(date) do
query =
from(
mh in MarketHistory,
diff --git a/apps/explorer/lib/explorer/chain/csv_export/helper.ex b/apps/explorer/lib/explorer/chain/csv_export/helper.ex
index eec8c85595de..92cf9687c427 100644
--- a/apps/explorer/lib/explorer/chain/csv_export/helper.ex
+++ b/apps/explorer/lib/explorer/chain/csv_export/helper.ex
@@ -3,22 +3,29 @@ defmodule Explorer.Chain.CSVExport.Helper do
CSV export helper functions.
"""
- alias Explorer.Chain
+ alias Explorer.{Chain, PagingOptions}
+ alias Explorer.Chain.Hash.Full, as: Hash
alias NimbleCSV.RFC4180
+ import Ecto.Query,
+ only: [
+ where: 3
+ ]
+
+ @limit 10_000
@page_size 150
+ @default_paging_options %PagingOptions{page_size: @page_size}
def dump_to_stream(items) do
- res =
- items
- |> RFC4180.dump_to_stream()
-
- res
+ items
+ |> RFC4180.dump_to_stream()
end
- def page_size do
- @page_size
- end
+ def page_size, do: @page_size
+
+ def default_paging_options, do: @default_paging_options
+
+ def limit, do: @limit
def block_from_period(from_period, to_period) do
from_block = Chain.convert_date_to_min_block(from_period)
@@ -26,4 +33,83 @@ defmodule Explorer.Chain.CSVExport.Helper do
{from_block, to_block}
end
+
+ def where_address_hash(query, address_hash, filter_type, filter_value) do
+ if filter_type == "address" do
+ case filter_value do
+ "to" -> where_address_hash_to(query, address_hash)
+ "from" -> where_address_hash_from(query, address_hash)
+ _ -> where_address_hash_all(query, address_hash)
+ end
+ else
+ where_address_hash_all(query, address_hash)
+ end
+ end
+
+ defp where_address_hash_to(query, address_hash) do
+ query
+ |> where(
+ [item],
+ item.to_address_hash == ^address_hash
+ )
+ end
+
+ defp where_address_hash_from(query, address_hash) do
+ query
+ |> where(
+ [item],
+ item.from_address_hash == ^address_hash
+ )
+ end
+
+ defp where_address_hash_all(query, address_hash) do
+ query
+ |> where(
+ [item],
+ item.to_address_hash == ^address_hash or
+ item.from_address_hash == ^address_hash or
+ item.token_contract_address_hash == ^address_hash
+ )
+ end
+
+ @spec supported_filters(String.t()) :: [String.t()]
+ def supported_filters(type) do
+ case type do
+ "internal-transactions" -> ["address"]
+ "transactions" -> ["address"]
+ "token-transfers" -> ["address"]
+ "logs" -> ["topic"]
+ _ -> []
+ end
+ end
+
+ @spec supported_address_filter_values() :: [String.t()]
+ def supported_address_filter_values do
+ ["to", "from"]
+ end
+
+ @spec is_valid_filter?(String.t(), String.t(), String.t()) :: boolean()
+ def is_valid_filter?(filter_type, filter_value, item_type) do
+ is_valid_filter_type(filter_type, filter_value, item_type) && is_valid_filter_value(filter_type, filter_value)
+ end
+
+ defp is_valid_filter_type(filter_type, filter_value, item_type) do
+ filter_type in supported_filters(item_type) && filter_value && filter_value !== ""
+ end
+
+ defp is_valid_filter_value(filter_type, filter_value) do
+ case filter_type do
+ "address" ->
+ filter_value in supported_address_filter_values()
+
+ "topic" ->
+ case Hash.cast(filter_value) do
+ {:ok, _} -> true
+ _ -> false
+ end
+
+ _ ->
+ true
+ end
+ end
end
diff --git a/apps/explorer/lib/explorer/chain/denormalization_helper.ex b/apps/explorer/lib/explorer/chain/denormalization_helper.ex
new file mode 100644
index 000000000000..0199fc7359e1
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/denormalization_helper.ex
@@ -0,0 +1,50 @@
+defmodule Explorer.Chain.DenormalizationHelper do
+ @moduledoc """
+ Helper functions for dynamic logic based on denormalization migration completeness
+ """
+
+ alias Explorer.Chain.Cache.BackgroundMigrations
+
+ @spec extend_block_necessity(keyword(), :optional | :required) :: keyword()
+ def extend_block_necessity(opts, necessity \\ :optional) do
+ if denormalization_finished?() do
+ opts
+ else
+ Keyword.update(opts, :necessity_by_association, %{:block => necessity}, &Map.put(&1, :block, necessity))
+ end
+ end
+
+ @spec extend_transaction_block_necessity(keyword(), :optional | :required) :: keyword()
+ def extend_transaction_block_necessity(opts, necessity \\ :optional) do
+ if denormalization_finished?() do
+ opts
+ else
+ Keyword.update(
+ opts,
+ :necessity_by_association,
+ %{[transaction: :block] => necessity},
+ &(&1 |> Map.delete(:transaction) |> Map.put([transaction: :block], necessity))
+ )
+ end
+ end
+
+ @spec extend_transaction_preload(list()) :: list()
+ def extend_transaction_preload(preloads) do
+ if denormalization_finished?() do
+ preloads
+ else
+ [transaction: :block] ++ (preloads -- [:transaction])
+ end
+ end
+
+ @spec extend_block_preload(list()) :: list()
+ def extend_block_preload(preloads) do
+ if denormalization_finished?() do
+ preloads
+ else
+ [:block | preloads]
+ end
+ end
+
+ def denormalization_finished?, do: BackgroundMigrations.get_denormalization_finished()
+end
diff --git a/apps/explorer/lib/explorer/chain/events/publisher.ex b/apps/explorer/lib/explorer/chain/events/publisher.ex
index 92f01bfc844d..3dca04f31f73 100644
--- a/apps/explorer/lib/explorer/chain/events/publisher.ex
+++ b/apps/explorer/lib/explorer/chain/events/publisher.ex
@@ -3,7 +3,7 @@ defmodule Explorer.Chain.Events.Publisher do
Publishes events related to the Chain context.
"""
- @allowed_events ~w(addresses address_coin_balances address_token_balances blocks block_rewards internal_transactions last_block_number token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified)a
+ @allowed_events ~w(addresses address_coin_balances address_token_balances address_current_token_balances blocks block_rewards internal_transactions last_block_number polygon_edge_reorg_block token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified zkevm_confirmed_batches eth_bytecode_db_lookup_started smart_contract_was_not_verified)a
def broadcast(_data, false), do: :ok
diff --git a/apps/explorer/lib/explorer/chain/events/subscriber.ex b/apps/explorer/lib/explorer/chain/events/subscriber.ex
index 9862e2e6c043..f2aa49f61fe3 100644
--- a/apps/explorer/lib/explorer/chain/events/subscriber.ex
+++ b/apps/explorer/lib/explorer/chain/events/subscriber.ex
@@ -3,7 +3,7 @@ defmodule Explorer.Chain.Events.Subscriber do
Subscribes to events related to the Chain context.
"""
- @allowed_broadcast_events ~w(addresses address_coin_balances address_token_balances blocks block_rewards internal_transactions last_block_number token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified)a
+ @allowed_broadcast_events ~w(addresses address_coin_balances address_token_balances address_current_token_balances blocks block_rewards internal_transactions last_block_number polygon_edge_reorg_block token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified zkevm_confirmed_batches eth_bytecode_db_lookup_started smart_contract_was_not_verified)a
@allowed_broadcast_types ~w(catchup realtime on_demand contract_verification_result)a
diff --git a/apps/explorer/lib/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand.ex b/apps/explorer/lib/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand.ex
index cc8b90c3cf0d..145b77368abb 100644
--- a/apps/explorer/lib/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand.ex
+++ b/apps/explorer/lib/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand.ex
@@ -5,7 +5,6 @@ defmodule Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand do
use GenServer
- alias Explorer.Chain
alias Explorer.Chain.{Address, Data, SmartContract}
alias Explorer.Chain.Events.Publisher
alias Explorer.SmartContract.EthBytecodeDBInterface
@@ -16,10 +15,16 @@ defmodule Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand do
@cache_name :smart_contracts_sources_fetching
+ @cooldown_timeout 500
+
def trigger_fetch(nil, _) do
:ignore
end
+ def trigger_fetch(address, %SmartContract{partially_verified: true}) do
+ GenServer.cast(__MODULE__, {:fetch, address})
+ end
+
def trigger_fetch(_address, %SmartContract{}) do
:ignore
end
@@ -28,21 +33,23 @@ defmodule Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand do
GenServer.cast(__MODULE__, {:fetch, address})
end
- defp fetch_sources(address) do
+ defp fetch_sources(address, only_full?) do
+ Publisher.broadcast(%{eth_bytecode_db_lookup_started: [address.hash]}, :on_demand)
+
creation_tx_input = contract_creation_input(address.hash)
- with {:ok, %{"sourceType" => type} = source} <-
+ with {:ok, %{"sourceType" => type, "matchType" => match_type} = source} <-
%{}
|> prepare_bytecode_for_microservice(creation_tx_input, Data.to_string(address.contract_code))
- |> EthBytecodeDBInterface.search_contract(),
+ |> EthBytecodeDBInterface.search_contract(address.hash),
+ :ok <- check_match_type(match_type, only_full?),
{:ok, _} <- process_contract_source(type, source, address.hash) do
Publisher.broadcast(%{smart_contract_was_verified: [address.hash]}, :on_demand)
else
_ ->
+ Publisher.broadcast(%{smart_contract_was_not_verified: [address.hash]}, :on_demand)
false
end
-
- :ets.insert(@cache_name, {to_string(address.hash), DateTime.utc_now()})
end
def start_link(_) do
@@ -50,35 +57,61 @@ defmodule Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand do
end
@impl true
- def init(opts) do
+ def init(_) do
:ets.new(@cache_name, [
:set,
:named_table,
:public
])
- {:ok, opts}
+ {:ok,
+ %{
+ current_concurrency: 0,
+ max_concurrency:
+ Application.get_env(:explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand)[:max_concurrency]
+ }}
end
@impl true
- def handle_cast({:fetch, address}, state) do
- if need_to_fetch_sources?(address) && check_interval(to_string(address.hash)) do
- fetch_sources(address)
- end
+ def handle_cast({:fetch, address}, %{current_concurrency: counter, max_concurrency: max_concurrency} = state)
+ when counter < max_concurrency do
+ handle_fetch_request(address, state)
+ end
+
+ @impl true
+ def handle_cast({:fetch, _address} = request, %{current_concurrency: _counter} = state) do
+ Process.send_after(self(), request, @cooldown_timeout)
{:noreply, state}
end
- defp need_to_fetch_sources?(%Address{smart_contract: nil}), do: true
+ @impl true
+ def handle_info({:fetch, address}, %{current_concurrency: counter, max_concurrency: max_concurrency} = state)
+ when counter < max_concurrency do
+ handle_fetch_request(address, state)
+ end
- defp need_to_fetch_sources?(%Address{hash: hash}) do
- case Chain.address_hash_to_one_smart_contract(hash) do
- nil ->
- true
+ @impl true
+ def handle_info({:fetch, _address} = request, state) do
+ Process.send_after(self(), request, @cooldown_timeout)
+ {:noreply, state}
+ end
- _ ->
- false
- end
+ @impl true
+ def handle_info({ref, _answer}, %{current_concurrency: counter} = state) do
+ Process.demonitor(ref, [:flush])
+ {:noreply, %{state | current_concurrency: counter - 1}}
+ end
+
+ @impl true
+ def handle_info({:DOWN, _ref, :process, _pid, _reason}, %{current_concurrency: counter} = state) do
+ {:noreply, %{state | current_concurrency: counter - 1}}
+ end
+
+ defp partially_verified?(%Address{smart_contract: nil}), do: nil
+
+ defp partially_verified?(%Address{hash: hash}) do
+ SmartContract.select_partially_verified_by_address_hash(hash)
end
defp check_interval(address_string) do
@@ -97,16 +130,41 @@ defmodule Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand do
end
def process_contract_source("SOLIDITY", source, address_hash) do
- SolidityPublisher.process_rust_verifier_response(source, address_hash, true, true)
+ SolidityPublisher.process_rust_verifier_response(source, address_hash, true, true, true)
end
def process_contract_source("VYPER", source, address_hash) do
- VyperPublisher.process_rust_verifier_response(source, address_hash, true)
+ VyperPublisher.process_rust_verifier_response(source, address_hash, true, true, true)
end
def process_contract_source("YUL", source, address_hash) do
- SolidityPublisher.process_rust_verifier_response(source, address_hash, true, true)
+ SolidityPublisher.process_rust_verifier_response(source, address_hash, true, true, true)
end
def process_contract_source(_, _source, _address_hash), do: false
+
+ defp check_match_type("PARTIAL", true), do: :full_match_required
+ defp check_match_type(_, _), do: :ok
+
+ defp handle_fetch_request(address, %{current_concurrency: counter} = state) do
+ need_to_check_and_partially_verified? =
+ check_interval(to_lowercase_string(address.hash)) && partially_verified?(address)
+
+ diff =
+ if is_nil(need_to_check_and_partially_verified?) || need_to_check_and_partially_verified? do
+ Task.Supervisor.async_nolink(Explorer.GenesisDataTaskSupervisor, fn ->
+ fetch_sources(address, need_to_check_and_partially_verified?)
+ end)
+
+ :ets.insert(@cache_name, {to_lowercase_string(address.hash), DateTime.utc_now()})
+
+ 1
+ else
+ 0
+ end
+
+ {:noreply, %{state | current_concurrency: counter + diff}}
+ end
+
+ defp to_lowercase_string(hash), do: hash |> to_string() |> String.downcase()
end
diff --git a/apps/explorer/lib/explorer/chain/hash/full.ex b/apps/explorer/lib/explorer/chain/hash/full.ex
index eb8ae148dd3a..db2a586bd770 100644
--- a/apps/explorer/lib/explorer/chain/hash/full.ex
+++ b/apps/explorer/lib/explorer/chain/hash/full.ex
@@ -90,7 +90,7 @@ defmodule Explorer.Chain.Hash.Full do
...> )
{:ok, <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: big-integer-size(32)-unit(8)>>}
- If the field from the struct is an incorrect format such as `t:Explorer.Chain.Address.Hash.t/0`, `:error` is returned.
+ If the field from the struct is an incorrect format such as `t:Explorer.Chain.Hash.Address.t/0`, `:error` is returned.
iex> Explorer.Chain.Hash.Full.dump(
...> %Explorer.Chain.Hash{
diff --git a/apps/explorer/lib/explorer/chain/import.ex b/apps/explorer/lib/explorer/chain/import.ex
index 8640d47e3a05..9372a55c01e8 100644
--- a/apps/explorer/lib/explorer/chain/import.ex
+++ b/apps/explorer/lib/explorer/chain/import.ex
@@ -12,8 +12,7 @@ defmodule Explorer.Chain.Import do
require Logger
@stages [
- Import.Stage.Addresses,
- Import.Stage.AddressReferencing,
+ Import.Stage.AddressesBlocksCoinBalances,
Import.Stage.BlockReferencing,
Import.Stage.BlockFollowing,
Import.Stage.BlockPending
@@ -321,6 +320,18 @@ defmodule Explorer.Chain.Import do
runner_to_changes_list
|> runner_to_changes_list_to_multis(options)
|> logged_import(options)
+ |> case do
+ {:ok, result} ->
+ {:ok, result}
+
+ error ->
+ remove_consensus_from_partially_imported_blocks(options)
+ error
+ end
+ rescue
+ exception ->
+ remove_consensus_from_partially_imported_blocks(options)
+ reraise exception, __STACKTRACE__
end
defp logged_import(multis, options) when is_list(multis) and is_map(options) do
@@ -348,6 +359,15 @@ defmodule Explorer.Chain.Import do
Repo.logged_transaction(multi, timeout: Map.get(options, :timeout, @transaction_timeout))
end
+ defp remove_consensus_from_partially_imported_blocks(%{blocks: %{params: blocks_params}}) do
+ block_numbers = Enum.map(blocks_params, & &1.number)
+ Import.Runner.Blocks.invalidate_consensus_blocks(block_numbers)
+
+ Logger.warning("Consensus removed from partially imported block because of error: #{inspect(block_numbers)}")
+ end
+
+ defp remove_consensus_from_partially_imported_blocks(_options), do: :ok
+
@spec timestamps() :: timestamps
def timestamps do
now = DateTime.utc_now()
diff --git a/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances_daily.ex b/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances_daily.ex
index 2c557fbf5152..e610ed3c81d9 100644
--- a/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances_daily.ex
+++ b/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances_daily.ex
@@ -77,18 +77,10 @@ defmodule Explorer.Chain.Import.Runner.Address.CoinBalancesDaily do
defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do
on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
- combined_changes_list =
- changes_list
- |> Enum.reduce([], fn change, acc ->
- if Enum.empty?(acc) do
- [change | acc]
- else
- compose_change(acc, change)
- end
- end)
+ combined_changes = changes_list |> Enum.reduce(%{}, &compose_change/2)
# Enforce CoinBalanceDaily ShareLocks order (see docs: sharelocks.md)
- ordered_changes_list = Enum.sort_by(combined_changes_list, &{&1.address_hash, &1.day})
+ ordered_changes_list = combined_changes |> Map.values() |> Enum.sort_by(&{&1.address_hash, &1.day})
{:ok, _} =
Import.insert_changes_list(
@@ -104,22 +96,15 @@ defmodule Explorer.Chain.Import.Runner.Address.CoinBalancesDaily do
{:ok, Enum.map(ordered_changes_list, &Map.take(&1, ~w(address_hash day)a))}
end
- defp compose_change(acc, change) do
- target_item =
- Enum.find(acc, fn item ->
- item.day == change.day && item.address_hash == change.address_hash
- end)
-
- if target_item do
- if Map.has_key?(change, :value) && Map.has_key?(target_item, :value) && change.value > target_item.value do
- acc_updated = List.delete(acc, target_item)
- [change | acc_updated]
+ defp compose_change(change, acc) do
+ Map.update(acc, {change.address_hash, change.day}, change, fn existing_change ->
+ if Map.has_key?(change, :value) && Map.has_key?(existing_change, :value) &&
+ change.value > existing_change.value do
+ change
else
- acc
+ existing_change
end
- else
- [change | acc]
- end
+ end)
end
def default_on_conflict do
diff --git a/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex b/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex
index b0d691190e5e..cc51fb87376c 100644
--- a/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex
+++ b/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex
@@ -107,35 +107,13 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do
|> Map.put_new(:timeout, @timeout)
|> Map.put(:timestamps, timestamps)
- # Enforce ShareLocks tables order (see docs: sharelocks.md)
- run_func = fn repo ->
- token_contract_address_hashes_and_ids =
- changes_list
- |> Enum.map(fn change ->
- token_id = get_token_id(change)
-
- {change.token_contract_address_hash, token_id}
- end)
- |> Enum.uniq()
-
- Tokens.acquire_contract_address_tokens(repo, token_contract_address_hashes_and_ids)
- end
-
multi
- |> Multi.run(:acquire_contract_address_tokens, fn repo, _ ->
- Instrumenter.block_import_stage_runner(
- fn -> run_func.(repo) end,
- :block_following,
- :current_token_balances,
- :acquire_contract_address_tokens
- )
- end)
|> Multi.run(:address_current_token_balances, fn repo, _ ->
Instrumenter.block_import_stage_runner(
fn -> insert(repo, changes_list, insert_options) end,
:block_following,
:current_token_balances,
- :acquire_contract_address_tokens
+ :address_current_token_balances
)
end)
|> Multi.run(:address_current_token_balances_update_token_holder_counts, fn repo,
@@ -156,15 +134,11 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do
end,
:block_following,
:current_token_balances,
- :acquire_contract_address_tokens
+ :address_current_token_balances_update_token_holder_counts
)
end)
end
- defp get_token_id(change) do
- if Map.has_key?(change, :token_id), do: change.token_id, else: nil
- end
-
@impl Import.Runner
def timeout, do: @timeout
@@ -299,11 +273,12 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do
]
],
where:
- fragment("? < EXCLUDED.block_number", current_token_balance.block_number) or
- (fragment("? = EXCLUDED.block_number", current_token_balance.block_number) and
- fragment("EXCLUDED.value IS NOT NULL") and
- (is_nil(current_token_balance.value_fetched_at) or
- fragment("? < EXCLUDED.value_fetched_at", current_token_balance.value_fetched_at)))
+ fragment("EXCLUDED.value_fetched_at IS NOT NULL") and
+ (fragment("? < EXCLUDED.block_number", current_token_balance.block_number) or
+ (fragment("? = EXCLUDED.block_number", current_token_balance.block_number) and
+ fragment("EXCLUDED.value IS NOT NULL") and
+ (is_nil(current_token_balance.value_fetched_at) or
+ fragment("? < EXCLUDED.value_fetched_at", current_token_balance.value_fetched_at))))
)
end
diff --git a/apps/explorer/lib/explorer/chain/import/runner/addresses.ex b/apps/explorer/lib/explorer/chain/import/runner/addresses.ex
index 2b703a0100fd..23a2e7a1d04b 100644
--- a/apps/explorer/lib/explorer/chain/import/runner/addresses.ex
+++ b/apps/explorer/lib/explorer/chain/import/runner/addresses.ex
@@ -58,19 +58,41 @@ defmodule Explorer.Chain.Import.Runner.Addresses do
end)
end)
+ ordered_changes_list =
+ changes_list_with_defaults
+ |> Enum.group_by(& &1.hash)
+ |> Enum.map(fn {_, grouped_addresses} ->
+ Enum.max_by(grouped_addresses, fn address ->
+ address_max_by(address)
+ end)
+ end)
+ |> Enum.sort_by(& &1.hash)
+
multi
- |> Multi.run(:addresses, fn repo, _ ->
+ |> Multi.run(:filter_addresses, fn repo, _ ->
Instrumenter.block_import_stage_runner(
- fn -> insert(repo, changes_list_with_defaults, insert_options) end,
+ fn -> filter_addresses(repo, ordered_changes_list) end,
+ :addresses,
+ :addresses,
+ :filter_addresses
+ )
+ end)
+ |> Multi.run(:addresses, fn repo, %{filter_addresses: {addresses, _existing_addresses}} ->
+ Instrumenter.block_import_stage_runner(
+ fn -> insert(repo, addresses, insert_options) end,
:addresses,
:addresses,
:addresses
)
end)
- |> Multi.run(:created_address_code_indexed_at_transactions, fn repo, %{addresses: addresses}
+ |> Multi.run(:created_address_code_indexed_at_transactions, fn repo,
+ %{
+ addresses: addresses,
+ filter_addresses: {_, existing_addresses_map}
+ }
when is_list(addresses) ->
Instrumenter.block_import_stage_runner(
- fn -> update_transactions(repo, addresses, update_transactions_options) end,
+ fn -> update_transactions(repo, addresses, existing_addresses_map, update_transactions_options) end,
:addresses,
:addresses,
:created_address_code_indexed_at_transactions
@@ -83,29 +105,56 @@ defmodule Explorer.Chain.Import.Runner.Addresses do
## Private Functions
+ @spec filter_addresses(Repo.t(), [map()]) :: {:ok, {[map()], map()}}
+ defp filter_addresses(repo, changes_list) do
+ hashes = Enum.map(changes_list, & &1.hash)
+
+ existing_addresses_query =
+ from(a in Address,
+ where: a.hash in ^hashes,
+ select: [:hash, :contract_code, :fetched_coin_balance_block_number, :nonce]
+ )
+
+ existing_addresses_map =
+ existing_addresses_query
+ |> repo.all()
+ |> Map.new(&{&1.hash, &1})
+
+ filtered_addresses =
+ changes_list
+ |> Enum.reduce([], fn address, acc ->
+ existing_address = existing_addresses_map[address.hash]
+
+ if should_update?(address, existing_address) do
+ [address | acc]
+ else
+ acc
+ end
+ end)
+ |> Enum.reverse()
+
+ {:ok, {filtered_addresses, existing_addresses_map}}
+ end
+
+ defp should_update?(new_address, existing_address) do
+ is_nil(existing_address) or
+ (not is_nil(new_address[:contract_code]) and new_address[:contract_code] != existing_address.contract_code) or
+ (not is_nil(new_address[:fetched_coin_balance_block_number]) and
+ (is_nil(existing_address.fetched_coin_balance_block_number) or
+ new_address[:fetched_coin_balance_block_number] >= existing_address.fetched_coin_balance_block_number)) or
+ (not is_nil(new_address[:nonce]) and
+ (is_nil(existing_address.nonce) or new_address[:nonce] > existing_address.nonce))
+ end
+
@spec insert(Repo.t(), [%{hash: Hash.Address.t()}], %{
optional(:on_conflict) => Import.Runner.on_conflict(),
required(:timeout) => timeout,
required(:timestamps) => Import.timestamps()
}) :: {:ok, [Address.t()]}
- defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do
+ defp insert(repo, ordered_changes_list, %{timeout: timeout, timestamps: timestamps} = options)
+ when is_list(ordered_changes_list) do
on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
- # Enforce Address ShareLocks order (see docs: sharelocks.md)
- ordered_changes_list =
- changes_list
- |> Enum.group_by(fn %{
- hash: hash
- } ->
- {hash}
- end)
- |> Enum.map(fn {_, grouped_addresses} ->
- Enum.max_by(grouped_addresses, fn address ->
- address_max_by(address)
- end)
- end)
- |> Enum.sort_by(& &1.hash)
-
Import.insert_changes_list(
repo,
ordered_changes_list,
@@ -175,10 +224,14 @@ defmodule Explorer.Chain.Import.Runner.Addresses do
)
end
- defp update_transactions(repo, addresses, %{timeout: timeout, timestamps: timestamps}) do
+ defp update_transactions(repo, addresses, existing_addresses_map, %{timeout: timeout, timestamps: timestamps}) do
ordered_created_contract_hashes =
addresses
- |> Enum.filter(& &1.contract_code)
+ |> Enum.filter(fn address ->
+ existing_address = existing_addresses_map[address.hash]
+
+ not is_nil(address.contract_code) and (is_nil(existing_address) or is_nil(existing_address.contract_code))
+ end)
|> MapSet.new(& &1.hash)
|> Enum.sort()
@@ -190,7 +243,7 @@ defmodule Explorer.Chain.Import.Runner.Addresses do
where: t.created_contract_address_hash in ^ordered_created_contract_hashes,
# Enforce Transaction ShareLocks order (see docs: sharelocks.md)
order_by: t.hash,
- lock: "FOR UPDATE"
+ lock: "FOR NO KEY UPDATE"
)
try do
diff --git a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex
index 3a4113dba7a6..4ed9d436d99e 100644
--- a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex
+++ b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex
@@ -8,11 +8,24 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
import Ecto.Query, only: [from: 2, where: 3, subquery: 1]
alias Ecto.{Changeset, Multi, Repo}
- alias Explorer.Chain.{Address, Block, Import, PendingBlockOperation, Transaction}
+
+ alias EthereumJSONRPC.Utility.RangesHelper
+
+ alias Explorer.Chain.{
+ Address,
+ Block,
+ Import,
+ PendingBlockOperation,
+ Token,
+ Token.Instance,
+ TokenTransfer,
+ Transaction
+ }
+
alias Explorer.Chain.Block.Reward
alias Explorer.Chain.Import.Runner
alias Explorer.Chain.Import.Runner.Address.CurrentTokenBalances
- alias Explorer.Chain.Import.Runner.Tokens
+ alias Explorer.Chain.Import.Runner.{TokenInstances, Tokens}
alias Explorer.Prometheus.Instrumenter
alias Explorer.Repo, as: ExplorerRepo
alias Explorer.Utility.MissingRangesManipulator
@@ -49,11 +62,9 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
hashes = Enum.map(changes_list, & &1.hash)
- minimal_block_height = trace_minimal_block_height()
-
items_for_pending_ops =
changes_list
- |> filter_by_min_height(&(&1.number >= minimal_block_height))
+ |> filter_by_height_range(&RangesHelper.traceable_block_number?(&1.number))
|> Enum.filter(& &1.consensus)
|> Enum.map(&{&1.number, &1.hash})
@@ -63,7 +74,8 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
run_func = fn repo ->
{:ok, nonconsensus_items} = lose_consensus(repo, hashes, consensus_block_numbers, changes_list, insert_options)
- {:ok, filter_by_min_height(nonconsensus_items, fn {number, _hash} -> number >= minimal_block_height end)}
+ {:ok,
+ filter_by_height_range(nonconsensus_items, fn {number, _hash} -> RangesHelper.traceable_block_number?(number) end)}
end
multi
@@ -149,25 +161,17 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
:derive_transaction_forks
)
end)
- |> Multi.run(:acquire_contract_address_tokens, fn repo, _ ->
- Instrumenter.block_import_stage_runner(
- fn -> acquire_contract_address_tokens(repo, consensus_block_numbers) end,
- :address_referencing,
- :blocks,
- :acquire_contract_address_tokens
- )
- end)
- |> Multi.run(:delete_address_token_balances, fn repo, _ ->
+ |> Multi.run(:delete_address_token_balances, fn repo, %{lose_consensus: non_consensus_blocks} ->
Instrumenter.block_import_stage_runner(
- fn -> delete_address_token_balances(repo, consensus_block_numbers, insert_options) end,
+ fn -> delete_address_token_balances(repo, non_consensus_blocks, insert_options) end,
:address_referencing,
:blocks,
:delete_address_token_balances
)
end)
- |> Multi.run(:delete_address_current_token_balances, fn repo, _ ->
+ |> Multi.run(:delete_address_current_token_balances, fn repo, %{lose_consensus: non_consensus_blocks} ->
Instrumenter.block_import_stage_runner(
- fn -> delete_address_current_token_balances(repo, consensus_block_numbers, insert_options) end,
+ fn -> delete_address_current_token_balances(repo, non_consensus_blocks, insert_options) end,
:address_referencing,
:blocks,
:delete_address_current_token_balances
@@ -185,6 +189,14 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
:derive_address_current_token_balances
)
end)
+ |> Multi.run(:update_token_instances_owner, fn repo, %{derive_transaction_forks: transactions} ->
+ Instrumenter.block_import_stage_runner(
+ fn -> update_token_instances_owner(repo, transactions, insert_options) end,
+ :address_referencing,
+ :blocks,
+ :update_token_instances_owner
+ )
+ end)
|> Multi.run(:blocks_update_token_holder_counts, fn repo,
%{
delete_address_current_token_balances: deleted,
@@ -205,19 +217,6 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
@impl Runner
def timeout, do: @timeout
- defp acquire_contract_address_tokens(repo, consensus_block_numbers) do
- query =
- from(ctb in Address.CurrentTokenBalance,
- where: ctb.block_number in ^consensus_block_numbers,
- select: {ctb.token_contract_address_hash, ctb.token_id},
- distinct: [ctb.token_contract_address_hash, ctb.token_id]
- )
-
- contract_address_hashes_and_token_ids = repo.all(query)
-
- Tokens.acquire_contract_address_tokens(repo, contract_address_hashes_and_token_ids)
- end
-
defp fork_transactions(%{
repo: repo,
timeout: timeout,
@@ -230,7 +229,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
select: transaction,
# Enforce Transaction ShareLocks order (see docs: sharelocks.md)
order_by: [asc: :hash],
- lock: "FOR UPDATE"
+ lock: "FOR NO KEY UPDATE"
)
update_query =
@@ -247,6 +246,9 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
index: nil,
status: nil,
error: nil,
+ max_priority_fee_per_gas: nil,
+ max_fee_per_gas: nil,
+ type: nil,
updated_at: ^updated_at
]
],
@@ -376,7 +378,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
select: block.hash,
# Enforce Block ShareLocks order (see docs: sharelocks.md)
order_by: [asc: block.hash],
- lock: "FOR UPDATE"
+ lock: "FOR NO KEY UPDATE"
)
{_, removed_consensus_block_hashes} =
@@ -393,8 +395,21 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
timeout: timeout
)
+ repo.update_all(
+ from(
+ transaction in Transaction,
+ join: s in subquery(acquire_query),
+ on: transaction.block_hash == s.hash,
+ # we don't want to remove consensus from blocks that will be upserted
+ where: transaction.block_hash not in ^hashes
+ ),
+ [set: [block_consensus: false, updated_at: updated_at]],
+ timeout: timeout
+ )
+
removed_consensus_block_hashes
|> Enum.map(fn {number, _hash} -> number end)
+ |> Enum.reject(&Enum.member?(consensus_block_numbers, &1))
|> MissingRangesManipulator.add_ranges_by_block_numbers()
{:ok, removed_consensus_block_hashes}
@@ -412,45 +427,39 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
lose_consensus(ExplorerRepo, [], block_numbers, [], opts)
end
- defp trace_minimal_block_height do
- EthereumJSONRPC.first_block_to_fetch(:trace_first_block)
- end
-
defp new_pending_operations(repo, nonconsensus_items, items, %{
timeout: timeout,
timestamps: timestamps
}) do
- if Application.get_env(:explorer, :json_rpc_named_arguments)[:variant] == EthereumJSONRPC.RSK do
- {:ok, []}
- else
- sorted_pending_ops =
- items
- |> MapSet.new()
- |> MapSet.difference(MapSet.new(nonconsensus_items))
- |> Enum.sort()
- |> Enum.map(fn {number, hash} ->
- %{block_hash: hash, block_number: number}
- end)
-
- Import.insert_changes_list(
- repo,
- sorted_pending_ops,
- conflict_target: :block_hash,
- on_conflict: :nothing,
- for: PendingBlockOperation,
- returning: true,
- timeout: timeout,
- timestamps: timestamps
- )
- end
+ sorted_pending_ops =
+ items
+ |> MapSet.new()
+ |> MapSet.difference(MapSet.new(nonconsensus_items))
+ |> Enum.sort()
+ |> Enum.map(fn {number, hash} ->
+ %{block_hash: hash, block_number: number}
+ end)
+
+ Import.insert_changes_list(
+ repo,
+ sorted_pending_ops,
+ conflict_target: :block_hash,
+ on_conflict: :nothing,
+ for: PendingBlockOperation,
+ returning: true,
+ timeout: timeout,
+ timestamps: timestamps
+ )
end
defp delete_address_token_balances(_, [], _), do: {:ok, []}
- defp delete_address_token_balances(repo, consensus_block_numbers, %{timeout: timeout}) do
+ defp delete_address_token_balances(repo, non_consensus_blocks, %{timeout: timeout}) do
+ non_consensus_block_numbers = Enum.map(non_consensus_blocks, fn {number, _hash} -> number end)
+
ordered_query =
from(tb in Address.TokenBalance,
- where: tb.block_number in ^consensus_block_numbers,
+ where: tb.block_number in ^non_consensus_block_numbers,
select: map(tb, [:address_hash, :token_contract_address_hash, :token_id, :block_number]),
# Enforce TokenBalance ShareLocks order (see docs: sharelocks.md)
order_by: [
@@ -482,16 +491,18 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
{:ok, deleted_address_token_balances}
rescue
postgrex_error in Postgrex.Error ->
- {:error, %{exception: postgrex_error, block_numbers: consensus_block_numbers}}
+ {:error, %{exception: postgrex_error, block_numbers: non_consensus_block_numbers}}
end
end
defp delete_address_current_token_balances(_, [], _), do: {:ok, []}
- defp delete_address_current_token_balances(repo, consensus_block_numbers, %{timeout: timeout}) do
+ defp delete_address_current_token_balances(repo, non_consensus_blocks, %{timeout: timeout}) do
+ non_consensus_block_numbers = Enum.map(non_consensus_blocks, fn {number, _hash} -> number end)
+
ordered_query =
from(ctb in Address.CurrentTokenBalance,
- where: ctb.block_number in ^consensus_block_numbers,
+ where: ctb.block_number in ^non_consensus_block_numbers,
select: map(ctb, [:address_hash, :token_contract_address_hash, :token_id]),
# Enforce CurrentTokenBalance ShareLocks order (see docs: sharelocks.md)
order_by: [
@@ -529,7 +540,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
{:ok, deleted_address_current_token_balances}
rescue
postgrex_error in Postgrex.Error ->
- {:error, %{exception: postgrex_error, block_numbers: consensus_block_numbers}}
+ {:error, %{exception: postgrex_error, block_numbers: non_consensus_block_numbers}}
end
end
@@ -557,8 +568,10 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
address_hash: new_current_token_balance.address_hash,
token_contract_address_hash: new_current_token_balance.token_contract_address_hash,
token_id: new_current_token_balance.token_id,
+ token_type: tb.token_type,
block_number: new_current_token_balance.block_number,
value: tb.value,
+ value_fetched_at: tb.value_fetched_at,
inserted_at: over(min(tb.inserted_at), :w),
updated_at: over(max(tb.updated_at), :w)
},
@@ -588,6 +601,155 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
{:ok, derived_address_current_token_balances}
end
+ defp update_token_instances_owner(_, [], _), do: {:ok, []}
+
+ defp update_token_instances_owner(repo, forked_transaction_hashes, options) do
+ forked_transaction_hashes
+ |> forked_token_transfers_query()
+ |> repo.all()
+ |> process_forked_token_transfers(repo, options)
+ end
+
+ defp process_forked_token_transfers([], _, _), do: {:ok, []}
+
+ defp process_forked_token_transfers(token_transfers, repo, options) do
+ changes_initial =
+ Enum.reduce(token_transfers, %{}, fn tt, acc ->
+ Map.put_new(acc, {tt.token_contract_address_hash, tt.token_id}, %{
+ token_contract_address_hash: tt.token_contract_address_hash,
+ token_id: tt.token_id,
+ owner_address_hash: tt.from,
+ owner_updated_at_block: -1,
+ owner_updated_at_log_index: -1
+ })
+ end)
+
+ non_consensus_block_numbers = token_transfers |> Enum.map(fn tt -> tt.block_number end) |> Enum.uniq()
+
+ filtered_query = TokenTransfer.only_consensus_transfers_query()
+
+ base_query =
+ from(token_transfer in subquery(filtered_query),
+ select: %{
+ token_contract_address_hash: token_transfer.token_contract_address_hash,
+ token_id: fragment("(?)[1]", token_transfer.token_ids),
+ block_number: max(token_transfer.block_number)
+ },
+ group_by: [token_transfer.token_contract_address_hash, fragment("(?)[1]", token_transfer.token_ids)]
+ )
+
+ historical_token_transfers_query =
+ Enum.reduce(token_transfers, base_query, fn tt, acc ->
+ from(token_transfer in acc,
+ or_where:
+ token_transfer.token_contract_address_hash == ^tt.token_contract_address_hash and
+ fragment("? @> ARRAY[?::decimal]", token_transfer.token_ids, ^tt.token_id) and
+ token_transfer.block_number < ^tt.block_number and
+ token_transfer.block_number not in ^non_consensus_block_numbers
+ )
+ end)
+
+ refs_to_token_transfers =
+ from(historical_tt in subquery(historical_token_transfers_query),
+ inner_join: tt in subquery(filtered_query),
+ on:
+ tt.token_contract_address_hash == historical_tt.token_contract_address_hash and
+ tt.block_number == historical_tt.block_number and
+ fragment("? @> ARRAY[?::decimal]", tt.token_ids, historical_tt.token_id),
+ select: %{
+ token_contract_address_hash: tt.token_contract_address_hash,
+ token_id: historical_tt.token_id,
+ log_index: max(tt.log_index),
+ block_number: tt.block_number
+ },
+ group_by: [tt.token_contract_address_hash, historical_tt.token_id, tt.block_number]
+ )
+
+ derived_token_transfers_query =
+ from(tt in filtered_query,
+ inner_join: tt_1 in subquery(refs_to_token_transfers),
+ on: tt_1.log_index == tt.log_index and tt_1.block_number == tt.block_number
+ )
+
+ changes =
+ derived_token_transfers_query
+ |> repo.all()
+ |> Enum.reduce(changes_initial, fn tt, acc ->
+ token_id = List.first(tt.token_ids)
+ current_key = {tt.token_contract_address_hash, token_id}
+
+ params = %{
+ token_contract_address_hash: tt.token_contract_address_hash,
+ token_id: token_id,
+ owner_address_hash: tt.to_address_hash,
+ owner_updated_at_block: tt.block_number,
+ owner_updated_at_log_index: tt.log_index
+ }
+
+ Map.put(
+ acc,
+ current_key,
+ Enum.max_by([acc[current_key], params], fn %{
+ owner_updated_at_block: block_number,
+ owner_updated_at_log_index: log_index
+ } ->
+ {block_number, log_index}
+ end)
+ )
+ end)
+ |> Map.values()
+
+ TokenInstances.insert(
+ repo,
+ changes,
+ options
+ |> Map.put(:timestamps, Import.timestamps())
+ |> Map.put(:on_conflict, token_instances_on_conflict())
+ )
+ end
+
+ defp forked_token_transfers_query(forked_transaction_hashes) do
+ from(token_transfer in TokenTransfer,
+ where: token_transfer.transaction_hash in ^forked_transaction_hashes,
+ inner_join: token in Token,
+ on: token.contract_address_hash == token_transfer.token_contract_address_hash,
+ where: token.type == "ERC-721",
+ inner_join: instance in Instance,
+ on:
+ fragment("? @> ARRAY[?::decimal]", token_transfer.token_ids, instance.token_id) and
+ instance.token_contract_address_hash == token_transfer.token_contract_address_hash,
+ # per one token instance we will have only one token transfer
+ where:
+ token_transfer.block_number == instance.owner_updated_at_block and
+ token_transfer.log_index == instance.owner_updated_at_log_index,
+ select: %{
+ from: token_transfer.from_address_hash,
+ to: token_transfer.to_address_hash,
+ token_id: instance.token_id,
+ token_contract_address_hash: token_transfer.token_contract_address_hash,
+ block_number: token_transfer.block_number,
+ log_index: token_transfer.log_index
+ }
+ )
+ end
+
+ defp token_instances_on_conflict do
+ from(
+ token_instance in Instance,
+ update: [
+ set: [
+ metadata: token_instance.metadata,
+ error: token_instance.error,
+ owner_updated_at_block: fragment("EXCLUDED.owner_updated_at_block"),
+ owner_updated_at_log_index: fragment("EXCLUDED.owner_updated_at_log_index"),
+ owner_address_hash: fragment("EXCLUDED.owner_address_hash"),
+ inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", token_instance.inserted_at),
+ updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", token_instance.updated_at)
+ ]
+ ]
+ )
+ end
+
defp derive_address_current_token_balances_grouped_query(deleted_address_current_token_balances) do
initial_query =
from(tb in Address.TokenBalance,
@@ -677,7 +839,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
where: bsdr.uncle_hash in ^uncle_hashes,
# Enforce SeconDegreeRelation ShareLocks order (see docs: sharelocks.md)
order_by: [asc: :nephew_hash, asc: :uncle_hash],
- lock: "FOR UPDATE"
+ lock: "FOR NO KEY UPDATE"
)
update_query =
@@ -736,10 +898,8 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
where(invalid_neighbors_query, [block], block.consensus)
end
- defp filter_by_min_height(blocks, filter_func) do
- minimal_block_height = trace_minimal_block_height()
-
- if minimal_block_height > 0 do
+ defp filter_by_height_range(blocks, filter_func) do
+ if RangesHelper.trace_ranges_present?() do
Enum.filter(blocks, &filter_func.(&1))
else
blocks
diff --git a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex
index 684e563c4a89..a4d512e602b8 100644
--- a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex
+++ b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex
@@ -8,6 +8,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
alias Ecto.Adapters.SQL
alias Ecto.{Changeset, Multi, Repo}
+ alias EthereumJSONRPC.Utility.RangesHelper
alias Explorer.Chain.{Block, Hash, Import, InternalTransaction, PendingBlockOperation, Transaction}
alias Explorer.Chain.Events.Publisher
alias Explorer.Chain.Import.Runner
@@ -15,7 +16,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
alias Explorer.Repo, as: ExplorerRepo
alias Explorer.Utility.MissingRangesManipulator
- import Ecto.Query, only: [from: 2]
+ import Ecto.Query
@behaviour Runner
@@ -282,11 +283,11 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
query =
from(
block in Block,
- where: block.number in ^block_numbers and block.consensus,
+ where: block.number in ^block_numbers and block.consensus == true,
select: block.hash,
# Enforce Block ShareLocks order (see docs: sharelocks.md)
order_by: [asc: block.hash],
- lock: "FOR UPDATE"
+ lock: "FOR NO KEY UPDATE"
)
{:ok, repo.all(query)}
@@ -311,10 +312,10 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
from(
t in Transaction,
where: t.block_hash in ^pending_block_hashes,
- select: map(t, [:hash, :block_hash, :block_number, :cumulative_gas_used]),
+ select: map(t, [:hash, :block_hash, :block_number, :cumulative_gas_used, :status]),
# Enforce Transaction ShareLocks order (see docs: sharelocks.md)
order_by: [asc: t.hash],
- lock: "FOR UPDATE"
+ lock: "FOR NO KEY UPDATE"
)
{:ok, repo.all(query)}
@@ -514,6 +515,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
timeout,
timestamps,
first_trace,
+ transaction_from_db,
transaction_receipt_from_node
)
@@ -525,7 +527,8 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
transaction_hashes_iterator,
timeout,
timestamps,
- first_trace
+ first_trace,
+ transaction_from_db
)
true ->
@@ -539,6 +542,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
timeout,
timestamps,
first_trace,
+ transaction_from_db,
transaction_receipt_from_node
)
end
@@ -579,6 +583,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
end
end
+ # credo:disable-for-next-line
defp update_transactions_inner(
repo,
valid_internal_transactions,
@@ -587,6 +592,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
timeout,
timestamps,
first_trace,
+ transaction_from_db,
transaction_receipt_from_node \\ nil
) do
valid_internal_transactions_count = Enum.count(valid_internal_transactions)
@@ -595,6 +601,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
set =
generate_transaction_set_to_update(
first_trace,
+ transaction_from_db,
transaction_receipt_from_node,
timestamps,
txs_with_error_in_internal_txs
@@ -628,19 +635,20 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
def generate_transaction_set_to_update(
first_trace,
+ transaction_from_db,
transaction_receipt_from_node,
timestamps,
txs_with_error_in_internal_txs
) do
default_set = [
created_contract_address_hash: first_trace.created_contract_address_hash,
- error: first_trace.error,
- status: first_trace.status,
updated_at: timestamps.updated_at
]
set =
default_set
+ |> put_status_in_update_set(first_trace, transaction_from_db)
+ |> put_error_in_update_set(first_trace, transaction_from_db, transaction_receipt_from_node)
|> Keyword.put_new(:block_hash, first_trace.block_hash)
|> Keyword.put_new(:block_number, first_trace.block_number)
|> Keyword.put_new(:index, transaction_receipt_from_node && transaction_receipt_from_node.transaction_index)
@@ -665,22 +673,48 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
filtered_set
end
- defp remove_consensus_of_invalid_blocks(repo, invalid_block_numbers) do
- minimal_block = EthereumJSONRPC.first_block_to_fetch(:trace_first_block)
+ defp put_status_in_update_set(update_set, first_trace, %{status: nil}),
+ do: Keyword.put_new(update_set, :status, first_trace.status)
+
+ defp put_status_in_update_set(update_set, _first_trace, _transaction_from_db), do: update_set
+
+ defp put_error_in_update_set(update_set, first_trace, _transaction_from_db, %{status: :error}),
+ do: Keyword.put_new(update_set, :error, first_trace.error)
+ defp put_error_in_update_set(update_set, first_trace, %{status: :error}, _transaction_receipt_from_node),
+ do: Keyword.put_new(update_set, :error, first_trace.error)
+
+ defp put_error_in_update_set(update_set, first_trace, _transaction_from_db, _transaction_receipt_from_node) do
+ case update_set[:status] do
+ :error -> Keyword.put_new(update_set, :error, first_trace.error)
+ _ -> update_set
+ end
+ end
+
+ defp remove_consensus_of_invalid_blocks(repo, invalid_block_numbers) do
if Enum.count(invalid_block_numbers) > 0 do
- update_query =
+ update_block_query =
from(
block in Block,
- where: block.number in ^invalid_block_numbers and block.consensus,
- where: block.number > ^minimal_block,
+ where: block.number in ^invalid_block_numbers and block.consensus == true,
+ where: ^traceable_blocks_dynamic_query(),
select: block.hash,
# ShareLocks order already enforced by `acquire_blocks` (see docs: sharelocks.md)
update: [set: [consensus: false]]
)
+ update_transaction_query =
+ from(
+ transaction in Transaction,
+ where: transaction.block_number in ^invalid_block_numbers and transaction.block_consensus,
+ where: ^traceable_transactions_dynamic_query(),
+ # ShareLocks order already enforced by `acquire_blocks` (see docs: sharelocks.md)
+ update: [set: [block_consensus: false]]
+ )
+
try do
- {_num, result} = repo.update_all(update_query, [])
+ {_num, result} = repo.update_all(update_block_query, [])
+ {_num, _result} = repo.update_all(update_transaction_query, [])
MissingRangesManipulator.add_ranges_by_block_numbers(invalid_block_numbers)
@@ -725,4 +759,30 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
{:error, %{exception: postgrex_error, pending_hashes: valid_block_hashes}}
end
end
+
+ defp traceable_blocks_dynamic_query do
+ if RangesHelper.trace_ranges_present?() do
+ block_ranges = RangesHelper.get_trace_block_ranges()
+
+ Enum.reduce(block_ranges, dynamic([_], false), fn
+ _from.._to = range, acc -> dynamic([block], ^acc or block.number in ^range)
+ num_to_latest, acc -> dynamic([block], ^acc or block.number >= ^num_to_latest)
+ end)
+ else
+ dynamic([_], true)
+ end
+ end
+
+ defp traceable_transactions_dynamic_query do
+ if RangesHelper.trace_ranges_present?() do
+ block_ranges = RangesHelper.get_trace_block_ranges()
+
+ Enum.reduce(block_ranges, dynamic([_], false), fn
+ _from.._to = range, acc -> dynamic([transaction], ^acc or transaction.block_number in ^range)
+ num_to_latest, acc -> dynamic([transaction], ^acc or transaction.block_number >= ^num_to_latest)
+ end)
+ else
+ dynamic([_], true)
+ end
+ end
end
diff --git a/apps/explorer/lib/explorer/chain/import/runner/logs.ex b/apps/explorer/lib/explorer/chain/import/runner/logs.ex
index c90adaa04a41..7c0e591a0f92 100644
--- a/apps/explorer/lib/explorer/chain/import/runner/logs.ex
+++ b/apps/explorer/lib/explorer/chain/import/runner/logs.ex
@@ -92,7 +92,6 @@ defmodule Explorer.Chain.Import.Runner.Logs do
third_topic: fragment("EXCLUDED.third_topic"),
fourth_topic: fragment("EXCLUDED.fourth_topic"),
# Don't update `index` as it is part of the composite primary key and used for the conflict target
- type: fragment("EXCLUDED.type"),
# Don't update `transaction_hash` as it is part of the composite primary key and used for the conflict target
inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", log.inserted_at),
updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", log.updated_at)
@@ -100,14 +99,13 @@ defmodule Explorer.Chain.Import.Runner.Logs do
],
where:
fragment(
- "(EXCLUDED.address_hash, EXCLUDED.data, EXCLUDED.first_topic, EXCLUDED.second_topic, EXCLUDED.third_topic, EXCLUDED.fourth_topic, EXCLUDED.type) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?)",
+ "(EXCLUDED.address_hash, EXCLUDED.data, EXCLUDED.first_topic, EXCLUDED.second_topic, EXCLUDED.third_topic, EXCLUDED.fourth_topic) IS DISTINCT FROM (?, ?, ?, ?, ?, ?)",
log.address_hash,
log.data,
log.first_topic,
log.second_topic,
log.third_topic,
- log.fourth_topic,
- log.type
+ log.fourth_topic
)
)
end
diff --git a/apps/explorer/lib/explorer/chain/import/runner/polygon_edge/deposit_executes.ex b/apps/explorer/lib/explorer/chain/import/runner/polygon_edge/deposit_executes.ex
new file mode 100644
index 000000000000..bc20a12567c2
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/import/runner/polygon_edge/deposit_executes.ex
@@ -0,0 +1,106 @@
+defmodule Explorer.Chain.Import.Runner.PolygonEdge.DepositExecutes do
+ @moduledoc """
+ Bulk imports `t:Explorer.Chain.PolygonEdge.DepositExecute.t/0`.
+ """
+
+ require Ecto.Query
+
+ import Ecto.Query, only: [from: 2]
+
+ alias Ecto.{Changeset, Multi, Repo}
+ alias Explorer.Chain.Import
+ alias Explorer.Chain.PolygonEdge.DepositExecute
+ alias Explorer.Prometheus.Instrumenter
+
+ @behaviour Import.Runner
+
+ # milliseconds
+ @timeout 60_000
+
+ @type imported :: [DepositExecute.t()]
+
+ @impl Import.Runner
+ def ecto_schema_module, do: DepositExecute
+
+ @impl Import.Runner
+ def option_key, do: :polygon_edge_deposit_executes
+
+ @impl Import.Runner
+ @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()}
+ def imported_table_row do
+ %{
+ value_type: "[#{ecto_schema_module()}.t()]",
+ value_description: "List of `t:#{ecto_schema_module()}.t/0`s"
+ }
+ end
+
+ @impl Import.Runner
+ @spec run(Multi.t(), list(), map()) :: Multi.t()
+ def run(multi, changes_list, %{timestamps: timestamps} = options) do
+ insert_options =
+ options
+ |> Map.get(option_key(), %{})
+ |> Map.take(~w(on_conflict timeout)a)
+ |> Map.put_new(:timeout, @timeout)
+ |> Map.put(:timestamps, timestamps)
+
+ Multi.run(multi, :insert_polygon_edge_deposit_executes, fn repo, _ ->
+ Instrumenter.block_import_stage_runner(
+ fn -> insert(repo, changes_list, insert_options) end,
+ :block_referencing,
+ :polygon_edge_deposit_executes,
+ :polygon_edge_deposit_executes
+ )
+ end)
+ end
+
+ @impl Import.Runner
+ def timeout, do: @timeout
+
+ @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) ::
+ {:ok, [DepositExecute.t()]}
+ | {:error, [Changeset.t()]}
+ def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do
+ on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
+
+ # Enforce PolygonEdge.DepositExecute ShareLocks order (see docs: sharelock.md)
+ ordered_changes_list = Enum.sort_by(changes_list, & &1.msg_id)
+
+ {:ok, inserted} =
+ Import.insert_changes_list(
+ repo,
+ ordered_changes_list,
+ conflict_target: :msg_id,
+ on_conflict: on_conflict,
+ for: DepositExecute,
+ returning: true,
+ timeout: timeout,
+ timestamps: timestamps
+ )
+
+ {:ok, inserted}
+ end
+
+ defp default_on_conflict do
+ from(
+ de in DepositExecute,
+ update: [
+ set: [
+ # Don't update `msg_id` as it is a primary key and used for the conflict target
+ l2_transaction_hash: fragment("EXCLUDED.l2_transaction_hash"),
+ l2_block_number: fragment("EXCLUDED.l2_block_number"),
+ success: fragment("EXCLUDED.success"),
+ inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", de.inserted_at),
+ updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", de.updated_at)
+ ]
+ ],
+ where:
+ fragment(
+ "(EXCLUDED.l2_transaction_hash, EXCLUDED.l2_block_number, EXCLUDED.success) IS DISTINCT FROM (?, ?, ?)",
+ de.l2_transaction_hash,
+ de.l2_block_number,
+ de.success
+ )
+ )
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/import/runner/polygon_edge/deposits.ex b/apps/explorer/lib/explorer/chain/import/runner/polygon_edge/deposits.ex
new file mode 100644
index 000000000000..b4893f86b67d
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/import/runner/polygon_edge/deposits.ex
@@ -0,0 +1,110 @@
+defmodule Explorer.Chain.Import.Runner.PolygonEdge.Deposits do
+ @moduledoc """
+ Bulk imports `t:Explorer.Chain.PolygonEdge.Deposit.t/0`.
+ """
+
+ require Ecto.Query
+
+ import Ecto.Query, only: [from: 2]
+
+ alias Ecto.{Changeset, Multi, Repo}
+ alias Explorer.Chain.Import
+ alias Explorer.Chain.PolygonEdge.Deposit
+ alias Explorer.Prometheus.Instrumenter
+
+ @behaviour Import.Runner
+
+ # milliseconds
+ @timeout 60_000
+
+ @type imported :: [Deposit.t()]
+
+ @impl Import.Runner
+ def ecto_schema_module, do: Deposit
+
+ @impl Import.Runner
+ def option_key, do: :polygon_edge_deposits
+
+ @impl Import.Runner
+ @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()}
+ def imported_table_row do
+ %{
+ value_type: "[#{ecto_schema_module()}.t()]",
+ value_description: "List of `t:#{ecto_schema_module()}.t/0`s"
+ }
+ end
+
+ @impl Import.Runner
+ @spec run(Multi.t(), list(), map()) :: Multi.t()
+ def run(multi, changes_list, %{timestamps: timestamps} = options) do
+ insert_options =
+ options
+ |> Map.get(option_key(), %{})
+ |> Map.take(~w(on_conflict timeout)a)
+ |> Map.put_new(:timeout, @timeout)
+ |> Map.put(:timestamps, timestamps)
+
+ Multi.run(multi, :insert_polygon_edge_deposits, fn repo, _ ->
+ Instrumenter.block_import_stage_runner(
+ fn -> insert(repo, changes_list, insert_options) end,
+ :block_referencing,
+ :polygon_edge_deposits,
+ :polygon_edge_deposits
+ )
+ end)
+ end
+
+ @impl Import.Runner
+ def timeout, do: @timeout
+
+ @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) ::
+ {:ok, [Deposit.t()]}
+ | {:error, [Changeset.t()]}
+ def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do
+ on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
+
+ # Enforce PolygonEdge.Deposit ShareLocks order (see docs: sharelock.md)
+ ordered_changes_list = Enum.sort_by(changes_list, & &1.msg_id)
+
+ {:ok, inserted} =
+ Import.insert_changes_list(
+ repo,
+ ordered_changes_list,
+ conflict_target: :msg_id,
+ on_conflict: on_conflict,
+ for: Deposit,
+ returning: true,
+ timeout: timeout,
+ timestamps: timestamps
+ )
+
+ {:ok, inserted}
+ end
+
+ defp default_on_conflict do
+ from(
+ d in Deposit,
+ update: [
+ set: [
+ # Don't update `msg_id` as it is a primary key and used for the conflict target
+ from: fragment("EXCLUDED.from"),
+ to: fragment("EXCLUDED.to"),
+ l1_transaction_hash: fragment("EXCLUDED.l1_transaction_hash"),
+ l1_timestamp: fragment("EXCLUDED.l1_timestamp"),
+ l1_block_number: fragment("EXCLUDED.l1_block_number"),
+ inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", d.inserted_at),
+ updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", d.updated_at)
+ ]
+ ],
+ where:
+ fragment(
+ "(EXCLUDED.from, EXCLUDED.to, EXCLUDED.l1_transaction_hash, EXCLUDED.l1_timestamp, EXCLUDED.l1_block_number) IS DISTINCT FROM (?, ?, ?, ?, ?)",
+ d.from,
+ d.to,
+ d.l1_transaction_hash,
+ d.l1_timestamp,
+ d.l1_block_number
+ )
+ )
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/import/runner/polygon_edge/withdrawal_exits.ex b/apps/explorer/lib/explorer/chain/import/runner/polygon_edge/withdrawal_exits.ex
new file mode 100644
index 000000000000..16c13eadbf1f
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/import/runner/polygon_edge/withdrawal_exits.ex
@@ -0,0 +1,106 @@
+defmodule Explorer.Chain.Import.Runner.PolygonEdge.WithdrawalExits do
+ @moduledoc """
+ Bulk imports `t:Explorer.Chain.PolygonEdge.WithdrawalExit.t/0`.
+ """
+
+ require Ecto.Query
+
+ import Ecto.Query, only: [from: 2]
+
+ alias Ecto.{Changeset, Multi, Repo}
+ alias Explorer.Chain.Import
+ alias Explorer.Chain.PolygonEdge.WithdrawalExit
+ alias Explorer.Prometheus.Instrumenter
+
+ @behaviour Import.Runner
+
+ # milliseconds
+ @timeout 60_000
+
+ @type imported :: [WithdrawalExit.t()]
+
+ @impl Import.Runner
+ def ecto_schema_module, do: WithdrawalExit
+
+ @impl Import.Runner
+ def option_key, do: :polygon_edge_withdrawal_exits
+
+ @impl Import.Runner
+ @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()}
+ def imported_table_row do
+ %{
+ value_type: "[#{ecto_schema_module()}.t()]",
+ value_description: "List of `t:#{ecto_schema_module()}.t/0`s"
+ }
+ end
+
+ @impl Import.Runner
+ @spec run(Multi.t(), list(), map()) :: Multi.t()
+ def run(multi, changes_list, %{timestamps: timestamps} = options) do
+ insert_options =
+ options
+ |> Map.get(option_key(), %{})
+ |> Map.take(~w(on_conflict timeout)a)
+ |> Map.put_new(:timeout, @timeout)
+ |> Map.put(:timestamps, timestamps)
+
+ Multi.run(multi, :insert_polygon_edge_withdrawal_exits, fn repo, _ ->
+ Instrumenter.block_import_stage_runner(
+ fn -> insert(repo, changes_list, insert_options) end,
+ :block_referencing,
+ :polygon_edge_withdrawal_exits,
+ :polygon_edge_withdrawal_exits
+ )
+ end)
+ end
+
+ @impl Import.Runner
+ def timeout, do: @timeout
+
+ @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) ::
+ {:ok, [WithdrawalExit.t()]}
+ | {:error, [Changeset.t()]}
+ def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do
+ on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
+
+ # Enforce PolygonEdge.WithdrawalExit ShareLocks order (see docs: sharelock.md)
+ ordered_changes_list = Enum.sort_by(changes_list, & &1.msg_id)
+
+ {:ok, inserted} =
+ Import.insert_changes_list(
+ repo,
+ ordered_changes_list,
+ conflict_target: :msg_id,
+ on_conflict: on_conflict,
+ for: WithdrawalExit,
+ returning: true,
+ timeout: timeout,
+ timestamps: timestamps
+ )
+
+ {:ok, inserted}
+ end
+
+ defp default_on_conflict do
+ from(
+ we in WithdrawalExit,
+ update: [
+ set: [
+ # Don't update `msg_id` as it is a primary key and used for the conflict target
+ l1_transaction_hash: fragment("EXCLUDED.l1_transaction_hash"),
+ l1_block_number: fragment("EXCLUDED.l1_block_number"),
+ success: fragment("EXCLUDED.success"),
+ inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", we.inserted_at),
+ updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", we.updated_at)
+ ]
+ ],
+ where:
+ fragment(
+ "(EXCLUDED.l1_transaction_hash, EXCLUDED.l1_block_number, EXCLUDED.success) IS DISTINCT FROM (?, ?, ?)",
+ we.l1_transaction_hash,
+ we.l1_block_number,
+ we.success
+ )
+ )
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/import/runner/polygon_edge/withdrawals.ex b/apps/explorer/lib/explorer/chain/import/runner/polygon_edge/withdrawals.ex
new file mode 100644
index 000000000000..e728e2cba495
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/import/runner/polygon_edge/withdrawals.ex
@@ -0,0 +1,108 @@
+defmodule Explorer.Chain.Import.Runner.PolygonEdge.Withdrawals do
+ @moduledoc """
+ Bulk imports `t:Explorer.Chain.PolygonEdge.Withdrawal.t/0`.
+ """
+
+ require Ecto.Query
+
+ import Ecto.Query, only: [from: 2]
+
+ alias Ecto.{Changeset, Multi, Repo}
+ alias Explorer.Chain.Import
+ alias Explorer.Chain.PolygonEdge.Withdrawal
+ alias Explorer.Prometheus.Instrumenter
+
+ @behaviour Import.Runner
+
+ # milliseconds
+ @timeout 60_000
+
+ @type imported :: [Withdrawal.t()]
+
+ @impl Import.Runner
+ def ecto_schema_module, do: Withdrawal
+
+ @impl Import.Runner
+ def option_key, do: :polygon_edge_withdrawals
+
+ @impl Import.Runner
+ @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()}
+ def imported_table_row do
+ %{
+ value_type: "[#{ecto_schema_module()}.t()]",
+ value_description: "List of `t:#{ecto_schema_module()}.t/0`s"
+ }
+ end
+
+ @impl Import.Runner
+ @spec run(Multi.t(), list(), map()) :: Multi.t()
+ def run(multi, changes_list, %{timestamps: timestamps} = options) do
+ insert_options =
+ options
+ |> Map.get(option_key(), %{})
+ |> Map.take(~w(on_conflict timeout)a)
+ |> Map.put_new(:timeout, @timeout)
+ |> Map.put(:timestamps, timestamps)
+
+ Multi.run(multi, :insert_polygon_edge_withdrawals, fn repo, _ ->
+ Instrumenter.block_import_stage_runner(
+ fn -> insert(repo, changes_list, insert_options) end,
+ :block_referencing,
+ :polygon_edge_withdrawals,
+ :polygon_edge_withdrawals
+ )
+ end)
+ end
+
+ @impl Import.Runner
+ def timeout, do: @timeout
+
+ @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) ::
+ {:ok, [Withdrawal.t()]}
+ | {:error, [Changeset.t()]}
+ def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do
+ on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
+
+ # Enforce PolygonEdge.Withdrawal ShareLocks order (see docs: sharelock.md)
+ ordered_changes_list = Enum.sort_by(changes_list, & &1.msg_id)
+
+ {:ok, inserted} =
+ Import.insert_changes_list(
+ repo,
+ ordered_changes_list,
+ conflict_target: :msg_id,
+ on_conflict: on_conflict,
+ for: Withdrawal,
+ returning: true,
+ timeout: timeout,
+ timestamps: timestamps
+ )
+
+ {:ok, inserted}
+ end
+
+ defp default_on_conflict do
+ from(
+ w in Withdrawal,
+ update: [
+ set: [
+ # Don't update `msg_id` as it is a primary key and used for the conflict target
+ from: fragment("EXCLUDED.from"),
+ to: fragment("EXCLUDED.to"),
+ l2_transaction_hash: fragment("EXCLUDED.l2_transaction_hash"),
+ l2_block_number: fragment("EXCLUDED.l2_block_number"),
+ inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", w.inserted_at),
+ updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", w.updated_at)
+ ]
+ ],
+ where:
+ fragment(
+ "(EXCLUDED.from, EXCLUDED.to, EXCLUDED.l2_transaction_hash, EXCLUDED.l2_block_number) IS DISTINCT FROM (?, ?, ?, ?)",
+ w.from,
+ w.to,
+ w.l2_transaction_hash,
+ w.l2_block_number
+ )
+ )
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/import/runner/token_instances.ex b/apps/explorer/lib/explorer/chain/import/runner/token_instances.ex
new file mode 100644
index 000000000000..0fb38962a755
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/import/runner/token_instances.ex
@@ -0,0 +1,106 @@
+defmodule Explorer.Chain.Import.Runner.TokenInstances do
+ @moduledoc """
+ Bulk imports `t:Explorer.Chain.TokenInstances.t/0`.
+ """
+
+ require Ecto.Query
+
+ alias Ecto.{Changeset, Multi, Repo}
+ alias Explorer.Chain.Import
+ alias Explorer.Chain.Token.Instance, as: TokenInstance
+ alias Explorer.Prometheus.Instrumenter
+
+ import Ecto.Query, only: [from: 2]
+
+ @behaviour Import.Runner
+
+ # milliseconds
+ @timeout 60_000
+
+ @type imported :: [TokenInstance.t()]
+
+ @impl Import.Runner
+ def ecto_schema_module, do: TokenInstance
+
+ @impl Import.Runner
+ def option_key, do: :token_instances
+
+ @impl Import.Runner
+ def imported_table_row do
+ %{
+ value_type: "[#{ecto_schema_module()}.t()]",
+ value_description: "List of `t:#{ecto_schema_module()}.t/0`s"
+ }
+ end
+
+ @impl Import.Runner
+ def run(multi, changes_list, %{timestamps: timestamps} = options) do
+ insert_options =
+ options
+ |> Map.get(option_key(), %{})
+ |> Map.take(~w(on_conflict timeout)a)
+ |> Map.put_new(:timeout, @timeout)
+ |> Map.put(:timestamps, timestamps)
+
+ Multi.run(multi, :token_instances, fn repo, _ ->
+ Instrumenter.block_import_stage_runner(
+ fn -> insert(repo, changes_list, insert_options) end,
+ :block_referencing,
+ :token_instances,
+ :token_instances
+ )
+ end)
+ end
+
+ @impl Import.Runner
+ def timeout, do: @timeout
+
+ @spec insert(Repo.t(), [map()], %{
+ optional(:on_conflict) => Import.Runner.on_conflict(),
+ required(:timeout) => timeout,
+ required(:timestamps) => Import.timestamps()
+ }) ::
+ {:ok, [TokenInstance.t()]}
+ | {:error, [Changeset.t()]}
+ def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do
+ on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
+
+ # Guarantee the same import order to avoid deadlocks
+ ordered_changes_list = Enum.sort_by(changes_list, &{&1.token_contract_address_hash, &1.token_id})
+
+ {:ok, _} =
+ Import.insert_changes_list(
+ repo,
+ ordered_changes_list,
+ conflict_target: [:token_contract_address_hash, :token_id],
+ on_conflict: on_conflict,
+ for: TokenInstance,
+ returning: true,
+ timeout: timeout,
+ timestamps: timestamps
+ )
+ end
+
+ defp default_on_conflict do
+ from(
+ token_instance in TokenInstance,
+ update: [
+ set: [
+ metadata: token_instance.metadata,
+ error: token_instance.error,
+ owner_updated_at_block: fragment("EXCLUDED.owner_updated_at_block"),
+ owner_updated_at_log_index: fragment("EXCLUDED.owner_updated_at_log_index"),
+ owner_address_hash: fragment("EXCLUDED.owner_address_hash"),
+ inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", token_instance.inserted_at),
+ updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", token_instance.updated_at)
+ ]
+ ],
+ where:
+ fragment("EXCLUDED.owner_address_hash IS NOT NULL") and fragment("EXCLUDED.owner_updated_at_block IS NOT NULL") and
+ (fragment("EXCLUDED.owner_updated_at_block > ?", token_instance.owner_updated_at_block) or
+ (fragment("EXCLUDED.owner_updated_at_block = ?", token_instance.owner_updated_at_block) and
+ fragment("EXCLUDED.owner_updated_at_log_index >= ?", token_instance.owner_updated_at_log_index)) or
+ is_nil(token_instance.owner_updated_at_block) or is_nil(token_instance.owner_address_hash))
+ )
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/import/runner/tokens.ex b/apps/explorer/lib/explorer/chain/import/runner/tokens.ex
index dbc45718d7cb..791ee9daa2f5 100644
--- a/apps/explorer/lib/explorer/chain/import/runner/tokens.ex
+++ b/apps/explorer/lib/explorer/chain/import/runner/tokens.ex
@@ -22,79 +22,10 @@ defmodule Explorer.Chain.Import.Runner.Tokens do
@type holder_count :: non_neg_integer()
@type token_holder_count :: %{contract_address_hash: Hash.Address.t(), count: holder_count()}
- def acquire_contract_address_tokens(repo, contract_address_hashes_and_token_ids) do
- initial_query_no_token_id =
- from(token in Token,
- select: token
- )
-
- initial_query_with_token_id =
- from(token in Token,
- left_join: instance in Token.Instance,
- on: token.contract_address_hash == instance.token_contract_address_hash,
- select: token
- )
-
- {query_no_token_id, query_with_token_id} =
- contract_address_hashes_and_token_ids
- |> Enum.reduce({initial_query_no_token_id, initial_query_with_token_id}, fn {contract_address_hash, token_id},
- {query_no_token_id,
- query_with_token_id} ->
- if is_nil(token_id) do
- {from(
- token in query_no_token_id,
- or_where: token.contract_address_hash == ^contract_address_hash
- ), query_with_token_id}
- else
- {query_no_token_id,
- from(
- [token, instance] in query_with_token_id,
- or_where: token.contract_address_hash == ^contract_address_hash and instance.token_id == ^token_id
- )}
- end
- end)
-
- final_query_no_token_id =
- if query_no_token_id == initial_query_no_token_id do
- nil
- else
- from(
- token in query_no_token_id,
- # Enforce Token ShareLocks order (see docs: sharelocks.md)
- order_by: [
- token.contract_address_hash
- ],
- lock: "FOR NO KEY UPDATE"
- )
- end
-
- final_query_with_token_id =
- if query_with_token_id == initial_query_with_token_id do
- nil
- else
- from(
- [token, instance] in query_with_token_id,
- # Enforce Token ShareLocks order (see docs: sharelocks.md)
- order_by: [
- token.contract_address_hash,
- instance.token_id
- ],
- lock: "FOR NO KEY UPDATE OF t0"
- )
- end
-
- tokens_no_token_id = (final_query_no_token_id && repo.all(final_query_no_token_id)) || []
- tokens_with_token_id = (final_query_with_token_id && repo.all(final_query_with_token_id)) || []
- tokens = tokens_no_token_id ++ tokens_with_token_id
-
- {:ok, tokens}
- end
-
def update_holder_counts_with_deltas(repo, token_holder_count_deltas, %{
timeout: timeout,
timestamps: %{updated_at: updated_at}
}) do
- # NOTE that acquire_contract_address_tokens needs to be called before this
{hashes, deltas} =
token_holder_count_deltas
|> Enum.map(fn %{contract_address_hash: contract_address_hash, delta: delta} ->
@@ -103,6 +34,15 @@ defmodule Explorer.Chain.Import.Runner.Tokens do
end)
|> Enum.unzip()
+ token_query =
+ from(
+ token in Token,
+ where: token.contract_address_hash in ^hashes,
+ select: token.contract_address_hash,
+ order_by: token.contract_address_hash,
+ lock: "FOR NO KEY UPDATE"
+ )
+
query =
from(
token in Token,
@@ -113,8 +53,8 @@ defmodule Explorer.Chain.Import.Runner.Tokens do
^deltas
),
on: token.contract_address_hash == deltas.contract_address_hash,
+ where: token.contract_address_hash in subquery(token_query),
where: not is_nil(token.holder_count),
- # ShareLocks order already enforced by `acquire_contract_address_tokens` (see docs: sharelocks.md)
update: [
set: [
holder_count: token.holder_count + deltas.delta,
@@ -201,13 +141,13 @@ defmodule Explorer.Chain.Import.Runner.Tokens do
token in Token,
update: [
set: [
- name: fragment("EXCLUDED.name"),
- symbol: fragment("EXCLUDED.symbol"),
- total_supply: fragment("EXCLUDED.total_supply"),
- decimals: fragment("EXCLUDED.decimals"),
- type: fragment("EXCLUDED.type"),
- cataloged: fragment("EXCLUDED.cataloged"),
- skip_metadata: fragment("EXCLUDED.skip_metadata"),
+ name: fragment("COALESCE(EXCLUDED.name, ?)", token.name),
+ symbol: fragment("COALESCE(EXCLUDED.symbol, ?)", token.symbol),
+ total_supply: fragment("COALESCE(EXCLUDED.total_supply, ?)", token.total_supply),
+ decimals: fragment("COALESCE(EXCLUDED.decimals, ?)", token.decimals),
+ type: fragment("COALESCE(EXCLUDED.type, ?)", token.type),
+ cataloged: fragment("COALESCE(EXCLUDED.cataloged, ?)", token.cataloged),
+ skip_metadata: fragment("COALESCE(EXCLUDED.skip_metadata, ?)", token.skip_metadata),
# `holder_count` is not updated as a pre-existing token means the `holder_count` is already initialized OR
# need to be migrated with `priv/repo/migrations/scripts/update_new_tokens_holder_count_in_batches.sql.exs`
# Don't update `contract_address_hash` as it is the primary key and used for the conflict target
diff --git a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex
index f35dc4300ddb..62578969a783 100644
--- a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex
+++ b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex
@@ -108,68 +108,169 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
end
defp default_on_conflict do
- from(
- transaction in Transaction,
- update: [
- set: [
- block_hash: fragment("EXCLUDED.block_hash"),
- old_block_hash: transaction.block_hash,
- block_number: fragment("EXCLUDED.block_number"),
- created_contract_address_hash: fragment("EXCLUDED.created_contract_address_hash"),
- created_contract_code_indexed_at: fragment("EXCLUDED.created_contract_code_indexed_at"),
- cumulative_gas_used: fragment("EXCLUDED.cumulative_gas_used"),
- error: fragment("EXCLUDED.error"),
- from_address_hash: fragment("EXCLUDED.from_address_hash"),
- gas: fragment("EXCLUDED.gas"),
- gas_price: fragment("EXCLUDED.gas_price"),
- gas_used: fragment("EXCLUDED.gas_used"),
- index: fragment("EXCLUDED.index"),
- input: fragment("EXCLUDED.input"),
- nonce: fragment("EXCLUDED.nonce"),
- r: fragment("EXCLUDED.r"),
- s: fragment("EXCLUDED.s"),
- status: fragment("EXCLUDED.status"),
- to_address_hash: fragment("EXCLUDED.to_address_hash"),
- v: fragment("EXCLUDED.v"),
- value: fragment("EXCLUDED.value"),
- earliest_processing_start: fragment("EXCLUDED.earliest_processing_start"),
- revert_reason: fragment("EXCLUDED.revert_reason"),
- max_priority_fee_per_gas: fragment("EXCLUDED.max_priority_fee_per_gas"),
- max_fee_per_gas: fragment("EXCLUDED.max_fee_per_gas"),
- type: fragment("EXCLUDED.type"),
- # Don't update `hash` as it is part of the primary key and used for the conflict target
- inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", transaction.inserted_at),
- updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", transaction.updated_at)
- ]
- ],
- where:
- fragment(
- "(EXCLUDED.block_hash, EXCLUDED.block_number, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code_indexed_at, EXCLUDED.cumulative_gas_used, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_price, EXCLUDED.gas_used, EXCLUDED.index, EXCLUDED.input, EXCLUDED.nonce, EXCLUDED.r, EXCLUDED.s, EXCLUDED.status, EXCLUDED.to_address_hash, EXCLUDED.v, EXCLUDED.value, EXCLUDED.earliest_processing_start, EXCLUDED.revert_reason, EXCLUDED.max_priority_fee_per_gas, EXCLUDED.max_fee_per_gas, EXCLUDED.type) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
- transaction.block_hash,
- transaction.block_number,
- transaction.created_contract_address_hash,
- transaction.created_contract_code_indexed_at,
- transaction.cumulative_gas_used,
- transaction.from_address_hash,
- transaction.gas,
- transaction.gas_price,
- transaction.gas_used,
- transaction.index,
- transaction.input,
- transaction.nonce,
- transaction.r,
- transaction.s,
- transaction.status,
- transaction.to_address_hash,
- transaction.v,
- transaction.value,
- transaction.earliest_processing_start,
- transaction.revert_reason,
- transaction.max_priority_fee_per_gas,
- transaction.max_fee_per_gas,
- transaction.type
- )
- )
+ if System.get_env("CHAIN_TYPE") == "suave" do
+ from(
+ transaction in Transaction,
+ update: [
+ set: [
+ block_hash: fragment("EXCLUDED.block_hash"),
+ old_block_hash: transaction.block_hash,
+ block_number: fragment("EXCLUDED.block_number"),
+ block_consensus: fragment("EXCLUDED.block_consensus"),
+ block_timestamp: fragment("EXCLUDED.block_timestamp"),
+ created_contract_address_hash: fragment("EXCLUDED.created_contract_address_hash"),
+ created_contract_code_indexed_at: fragment("EXCLUDED.created_contract_code_indexed_at"),
+ cumulative_gas_used: fragment("EXCLUDED.cumulative_gas_used"),
+ error: fragment("EXCLUDED.error"),
+ from_address_hash: fragment("EXCLUDED.from_address_hash"),
+ gas: fragment("EXCLUDED.gas"),
+ gas_price: fragment("EXCLUDED.gas_price"),
+ gas_used: fragment("EXCLUDED.gas_used"),
+ index: fragment("EXCLUDED.index"),
+ input: fragment("EXCLUDED.input"),
+ nonce: fragment("EXCLUDED.nonce"),
+ r: fragment("EXCLUDED.r"),
+ s: fragment("EXCLUDED.s"),
+ status: fragment("EXCLUDED.status"),
+ to_address_hash: fragment("EXCLUDED.to_address_hash"),
+ v: fragment("EXCLUDED.v"),
+ value: fragment("EXCLUDED.value"),
+ earliest_processing_start: fragment("EXCLUDED.earliest_processing_start"),
+ revert_reason: fragment("EXCLUDED.revert_reason"),
+ max_priority_fee_per_gas: fragment("EXCLUDED.max_priority_fee_per_gas"),
+ max_fee_per_gas: fragment("EXCLUDED.max_fee_per_gas"),
+ type: fragment("EXCLUDED.type"),
+ execution_node_hash: fragment("EXCLUDED.execution_node_hash"),
+ wrapped_type: fragment("EXCLUDED.wrapped_type"),
+ wrapped_nonce: fragment("EXCLUDED.wrapped_nonce"),
+ wrapped_to_address_hash: fragment("EXCLUDED.wrapped_to_address_hash"),
+ wrapped_gas: fragment("EXCLUDED.wrapped_gas"),
+ wrapped_gas_price: fragment("EXCLUDED.wrapped_gas_price"),
+ wrapped_max_priority_fee_per_gas: fragment("EXCLUDED.wrapped_max_priority_fee_per_gas"),
+ wrapped_max_fee_per_gas: fragment("EXCLUDED.wrapped_max_fee_per_gas"),
+ wrapped_value: fragment("EXCLUDED.wrapped_value"),
+ wrapped_input: fragment("EXCLUDED.wrapped_input"),
+ wrapped_v: fragment("EXCLUDED.wrapped_v"),
+ wrapped_r: fragment("EXCLUDED.wrapped_r"),
+ wrapped_s: fragment("EXCLUDED.wrapped_s"),
+ wrapped_hash: fragment("EXCLUDED.wrapped_hash"),
+ # Don't update `hash` as it is part of the primary key and used for the conflict target
+ inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", transaction.inserted_at),
+ updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", transaction.updated_at)
+ ]
+ ],
+ where:
+ fragment(
+ "(EXCLUDED.block_hash, EXCLUDED.block_number, EXCLUDED.block_consensus, EXCLUDED.block_timestamp, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code_indexed_at, EXCLUDED.cumulative_gas_used, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_price, EXCLUDED.gas_used, EXCLUDED.index, EXCLUDED.input, EXCLUDED.nonce, EXCLUDED.r, EXCLUDED.s, EXCLUDED.status, EXCLUDED.to_address_hash, EXCLUDED.v, EXCLUDED.value, EXCLUDED.earliest_processing_start, EXCLUDED.revert_reason, EXCLUDED.max_priority_fee_per_gas, EXCLUDED.max_fee_per_gas, EXCLUDED.type, EXCLUDED.execution_node_hash, EXCLUDED.wrapped_type, EXCLUDED.wrapped_nonce, EXCLUDED.wrapped_to_address_hash, EXCLUDED.wrapped_gas, EXCLUDED.wrapped_gas_price, EXCLUDED.wrapped_max_priority_fee_per_gas, EXCLUDED.wrapped_max_fee_per_gas, EXCLUDED.wrapped_value, EXCLUDED.wrapped_input, EXCLUDED.wrapped_v, EXCLUDED.wrapped_r, EXCLUDED.wrapped_s, EXCLUDED.wrapped_hash) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ transaction.block_hash,
+ transaction.block_number,
+ transaction.block_consensus,
+ transaction.block_timestamp,
+ transaction.created_contract_address_hash,
+ transaction.created_contract_code_indexed_at,
+ transaction.cumulative_gas_used,
+ transaction.from_address_hash,
+ transaction.gas,
+ transaction.gas_price,
+ transaction.gas_used,
+ transaction.index,
+ transaction.input,
+ transaction.nonce,
+ transaction.r,
+ transaction.s,
+ transaction.status,
+ transaction.to_address_hash,
+ transaction.v,
+ transaction.value,
+ transaction.earliest_processing_start,
+ transaction.revert_reason,
+ transaction.max_priority_fee_per_gas,
+ transaction.max_fee_per_gas,
+ transaction.type,
+ transaction.execution_node_hash,
+ transaction.wrapped_type,
+ transaction.wrapped_nonce,
+ transaction.wrapped_to_address_hash,
+ transaction.wrapped_gas,
+ transaction.wrapped_gas_price,
+ transaction.wrapped_max_priority_fee_per_gas,
+ transaction.wrapped_max_fee_per_gas,
+ transaction.wrapped_value,
+ transaction.wrapped_input,
+ transaction.wrapped_v,
+ transaction.wrapped_r,
+ transaction.wrapped_s,
+ transaction.wrapped_hash
+ )
+ )
+ else
+ from(
+ transaction in Transaction,
+ update: [
+ set: [
+ block_hash: fragment("EXCLUDED.block_hash"),
+ old_block_hash: transaction.block_hash,
+ block_number: fragment("EXCLUDED.block_number"),
+ block_consensus: fragment("EXCLUDED.block_consensus"),
+ block_timestamp: fragment("EXCLUDED.block_timestamp"),
+ created_contract_address_hash: fragment("EXCLUDED.created_contract_address_hash"),
+ created_contract_code_indexed_at: fragment("EXCLUDED.created_contract_code_indexed_at"),
+ cumulative_gas_used: fragment("EXCLUDED.cumulative_gas_used"),
+ error: fragment("EXCLUDED.error"),
+ from_address_hash: fragment("EXCLUDED.from_address_hash"),
+ gas: fragment("EXCLUDED.gas"),
+ gas_price: fragment("EXCLUDED.gas_price"),
+ gas_used: fragment("EXCLUDED.gas_used"),
+ index: fragment("EXCLUDED.index"),
+ input: fragment("EXCLUDED.input"),
+ nonce: fragment("EXCLUDED.nonce"),
+ r: fragment("EXCLUDED.r"),
+ s: fragment("EXCLUDED.s"),
+ status: fragment("EXCLUDED.status"),
+ to_address_hash: fragment("EXCLUDED.to_address_hash"),
+ v: fragment("EXCLUDED.v"),
+ value: fragment("EXCLUDED.value"),
+ earliest_processing_start: fragment("EXCLUDED.earliest_processing_start"),
+ revert_reason: fragment("EXCLUDED.revert_reason"),
+ max_priority_fee_per_gas: fragment("EXCLUDED.max_priority_fee_per_gas"),
+ max_fee_per_gas: fragment("EXCLUDED.max_fee_per_gas"),
+ type: fragment("EXCLUDED.type"),
+ # Don't update `hash` as it is part of the primary key and used for the conflict target
+ inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", transaction.inserted_at),
+ updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", transaction.updated_at)
+ ]
+ ],
+ where:
+ fragment(
+ "(EXCLUDED.block_hash, EXCLUDED.block_number, EXCLUDED.block_consensus, EXCLUDED.block_timestamp, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code_indexed_at, EXCLUDED.cumulative_gas_used, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_price, EXCLUDED.gas_used, EXCLUDED.index, EXCLUDED.input, EXCLUDED.nonce, EXCLUDED.r, EXCLUDED.s, EXCLUDED.status, EXCLUDED.to_address_hash, EXCLUDED.v, EXCLUDED.value, EXCLUDED.earliest_processing_start, EXCLUDED.revert_reason, EXCLUDED.max_priority_fee_per_gas, EXCLUDED.max_fee_per_gas, EXCLUDED.type) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ transaction.block_hash,
+ transaction.block_number,
+ transaction.block_consensus,
+ transaction.block_timestamp,
+ transaction.created_contract_address_hash,
+ transaction.created_contract_code_indexed_at,
+ transaction.cumulative_gas_used,
+ transaction.from_address_hash,
+ transaction.gas,
+ transaction.gas_price,
+ transaction.gas_used,
+ transaction.index,
+ transaction.input,
+ transaction.nonce,
+ transaction.r,
+ transaction.s,
+ transaction.status,
+ transaction.to_address_hash,
+ transaction.v,
+ transaction.value,
+ transaction.earliest_processing_start,
+ transaction.revert_reason,
+ transaction.max_priority_fee_per_gas,
+ transaction.max_fee_per_gas,
+ transaction.type
+ )
+ )
+ end
end
defp discard_blocks_for_recollated_transactions(repo, changes_list, %{
@@ -198,14 +299,20 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
),
on: transaction.hash == new_transaction.hash,
where: transaction.block_hash != new_transaction.block_hash,
- select: transaction.block_hash
+ select: %{hash: transaction.hash, block_hash: transaction.block_hash}
)
block_hashes =
blocks_with_recollated_transactions
|> repo.all()
+ |> Enum.map(fn %{block_hash: block_hash} -> block_hash end)
|> Enum.uniq()
+ transaction_hashes =
+ blocks_with_recollated_transactions
+ |> repo.all()
+ |> Enum.map(fn %{hash: hash} -> hash end)
+
if Enum.empty?(block_hashes) do
{:ok, []}
else
@@ -215,9 +322,32 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
where: block.hash in ^block_hashes,
# Enforce Block ShareLocks order (see docs: sharelocks.md)
order_by: [asc: block.hash],
- lock: "FOR UPDATE"
+ lock: "FOR NO KEY UPDATE"
)
+ transactions_query =
+ from(
+ transaction in Transaction,
+ where: transaction.block_hash in ^block_hashes,
+ # Enforce Transaction ShareLocks order (see docs: sharelocks.md)
+ order_by: [asc: :hash],
+ lock: "FOR NO KEY UPDATE"
+ )
+
+ transactions_replacements = [
+ block_hash: nil,
+ block_number: nil,
+ gas_used: nil,
+ cumulative_gas_used: nil,
+ index: nil,
+ status: nil,
+ error: nil,
+ max_priority_fee_per_gas: nil,
+ max_fee_per_gas: nil,
+ type: nil,
+ updated_at: updated_at
+ ]
+
try do
{_, result} =
repo.update_all(
@@ -226,6 +356,13 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
timeout: timeout
)
+ {_, _transactions_result} =
+ repo.update_all(
+ from(t in Transaction, join: s in subquery(transactions_query), on: t.hash == s.hash),
+ [set: transactions_replacements],
+ timeout: timeout
+ )
+
MissingRangesManipulator.add_ranges_by_block_numbers(result)
{:ok, result}
@@ -234,5 +371,32 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
{:error, %{exception: postgrex_error, block_hashes: block_hashes}}
end
end
+
+ if Enum.empty?(transaction_hashes) do
+ {:ok, []}
+ else
+ query =
+ from(
+ transaction in Transaction,
+ where: transaction.hash in ^transaction_hashes,
+ # Enforce Block ShareLocks order (see docs: sharelocks.md)
+ order_by: [asc: transaction.hash],
+ lock: "FOR UPDATE"
+ )
+
+ try do
+ {_, result} =
+ repo.update_all(
+ from(transaction in Transaction, join: s in subquery(query), on: transaction.hash == s.hash),
+ [set: [block_consensus: false, updated_at: updated_at]],
+ timeout: timeout
+ )
+
+ {:ok, result}
+ rescue
+ postgrex_error in Postgrex.Error ->
+ {:error, %{exception: postgrex_error, transaction_hashes: transaction_hashes}}
+ end
+ end
end
end
diff --git a/apps/explorer/lib/explorer/chain/import/runner/zkevm/batch_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/zkevm/batch_transactions.ex
new file mode 100644
index 000000000000..2df1223945a1
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/import/runner/zkevm/batch_transactions.ex
@@ -0,0 +1,79 @@
+defmodule Explorer.Chain.Import.Runner.Zkevm.BatchTransactions do
+ @moduledoc """
+ Bulk imports `t:Explorer.Chain.Zkevm.BatchTransaction.t/0`.
+ """
+
+ require Ecto.Query
+
+ alias Ecto.{Changeset, Multi, Repo}
+ alias Explorer.Chain.Import
+ alias Explorer.Chain.Zkevm.BatchTransaction
+ alias Explorer.Prometheus.Instrumenter
+
+ @behaviour Import.Runner
+
+ # milliseconds
+ @timeout 60_000
+
+ @type imported :: [BatchTransaction.t()]
+
+ @impl Import.Runner
+ def ecto_schema_module, do: BatchTransaction
+
+ @impl Import.Runner
+ def option_key, do: :zkevm_batch_transactions
+
+ @impl Import.Runner
+ @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()}
+ def imported_table_row do
+ %{
+ value_type: "[#{ecto_schema_module()}.t()]",
+ value_description: "List of `t:#{ecto_schema_module()}.t/0`s"
+ }
+ end
+
+ @impl Import.Runner
+ @spec run(Multi.t(), list(), map()) :: Multi.t()
+ def run(multi, changes_list, %{timestamps: timestamps} = options) do
+ insert_options =
+ options
+ |> Map.get(option_key(), %{})
+ |> Map.take(~w(on_conflict timeout)a)
+ |> Map.put_new(:timeout, @timeout)
+ |> Map.put(:timestamps, timestamps)
+
+ Multi.run(multi, :insert_zkevm_batch_transactions, fn repo, _ ->
+ Instrumenter.block_import_stage_runner(
+ fn -> insert(repo, changes_list, insert_options) end,
+ :block_referencing,
+ :zkevm_batch_transactions,
+ :zkevm_batch_transactions
+ )
+ end)
+ end
+
+ @impl Import.Runner
+ def timeout, do: @timeout
+
+ @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) ::
+ {:ok, [BatchTransaction.t()]}
+ | {:error, [Changeset.t()]}
+ def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = _options) when is_list(changes_list) do
+ # Enforce Zkevm.BatchTransaction ShareLocks order (see docs: sharelock.md)
+ ordered_changes_list = Enum.sort_by(changes_list, & &1.hash)
+
+ {:ok, inserted} =
+ Import.insert_changes_list(
+ repo,
+ ordered_changes_list,
+ for: BatchTransaction,
+ returning: true,
+ timeout: timeout,
+ timestamps: timestamps,
+ conflict_target: :hash,
+ on_conflict: :nothing
+ )
+
+ {:ok, inserted}
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/import/runner/zkevm/lifecycle_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/zkevm/lifecycle_transactions.ex
new file mode 100644
index 000000000000..7a5e4c132735
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/import/runner/zkevm/lifecycle_transactions.ex
@@ -0,0 +1,103 @@
+defmodule Explorer.Chain.Import.Runner.Zkevm.LifecycleTransactions do
+ @moduledoc """
+ Bulk imports `t:Explorer.Chain.Zkevm.LifecycleTransaction.t/0`.
+ """
+
+ require Ecto.Query
+
+ alias Ecto.{Changeset, Multi, Repo}
+ alias Explorer.Chain.Import
+ alias Explorer.Chain.Zkevm.LifecycleTransaction
+ alias Explorer.Prometheus.Instrumenter
+
+ import Ecto.Query, only: [from: 2]
+
+ @behaviour Import.Runner
+
+ # milliseconds
+ @timeout 60_000
+
+ @type imported :: [LifecycleTransaction.t()]
+
+ @impl Import.Runner
+ def ecto_schema_module, do: LifecycleTransaction
+
+ @impl Import.Runner
+ def option_key, do: :zkevm_lifecycle_transactions
+
+ @impl Import.Runner
+ @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()}
+ def imported_table_row do
+ %{
+ value_type: "[#{ecto_schema_module()}.t()]",
+ value_description: "List of `t:#{ecto_schema_module()}.t/0`s"
+ }
+ end
+
+ @impl Import.Runner
+ @spec run(Multi.t(), list(), map()) :: Multi.t()
+ def run(multi, changes_list, %{timestamps: timestamps} = options) do
+ insert_options =
+ options
+ |> Map.get(option_key(), %{})
+ |> Map.take(~w(on_conflict timeout)a)
+ |> Map.put_new(:timeout, @timeout)
+ |> Map.put(:timestamps, timestamps)
+
+ Multi.run(multi, :insert_zkevm_lifecycle_transactions, fn repo, _ ->
+ Instrumenter.block_import_stage_runner(
+ fn -> insert(repo, changes_list, insert_options) end,
+ :block_referencing,
+ :zkevm_lifecycle_transactions,
+ :zkevm_lifecycle_transactions
+ )
+ end)
+ end
+
+ @impl Import.Runner
+ def timeout, do: @timeout
+
+ @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) ::
+ {:ok, [LifecycleTransaction.t()]}
+ | {:error, [Changeset.t()]}
+ def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do
+ on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
+
+ # Enforce Zkevm.LifecycleTransaction ShareLocks order (see docs: sharelock.md)
+ ordered_changes_list = Enum.sort_by(changes_list, & &1.id)
+
+ {:ok, inserted} =
+ Import.insert_changes_list(
+ repo,
+ ordered_changes_list,
+ for: LifecycleTransaction,
+ returning: true,
+ timeout: timeout,
+ timestamps: timestamps,
+ conflict_target: :hash,
+ on_conflict: on_conflict
+ )
+
+ {:ok, inserted}
+ end
+
+ defp default_on_conflict do
+ from(
+ tx in LifecycleTransaction,
+ update: [
+ set: [
+ # don't update `id` as it is a primary key
+ # don't update `hash` as it is a unique index and used for the conflict target
+ is_verify: fragment("EXCLUDED.is_verify"),
+ inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", tx.inserted_at),
+ updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", tx.updated_at)
+ ]
+ ],
+ where:
+ fragment(
+ "(EXCLUDED.is_verify) IS DISTINCT FROM (?)",
+ tx.is_verify
+ )
+ )
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/import/runner/zkevm/transaction_batches.ex b/apps/explorer/lib/explorer/chain/import/runner/zkevm/transaction_batches.ex
new file mode 100644
index 000000000000..db9f2771ea40
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/import/runner/zkevm/transaction_batches.ex
@@ -0,0 +1,114 @@
+defmodule Explorer.Chain.Import.Runner.Zkevm.TransactionBatches do
+ @moduledoc """
+ Bulk imports `t:Explorer.Chain.Zkevm.TransactionBatch.t/0`.
+ """
+
+ require Ecto.Query
+
+ alias Ecto.{Changeset, Multi, Repo}
+ alias Explorer.Chain.Import
+ alias Explorer.Chain.Zkevm.TransactionBatch
+ alias Explorer.Prometheus.Instrumenter
+
+ import Ecto.Query, only: [from: 2]
+
+ @behaviour Import.Runner
+
+ # milliseconds
+ @timeout 60_000
+
+ @type imported :: [TransactionBatch.t()]
+
+ @impl Import.Runner
+ def ecto_schema_module, do: TransactionBatch
+
+ @impl Import.Runner
+ def option_key, do: :zkevm_transaction_batches
+
+ @impl Import.Runner
+ @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()}
+ def imported_table_row do
+ %{
+ value_type: "[#{ecto_schema_module()}.t()]",
+ value_description: "List of `t:#{ecto_schema_module()}.t/0`s"
+ }
+ end
+
+ @impl Import.Runner
+ @spec run(Multi.t(), list(), map()) :: Multi.t()
+ def run(multi, changes_list, %{timestamps: timestamps} = options) do
+ insert_options =
+ options
+ |> Map.get(option_key(), %{})
+ |> Map.take(~w(on_conflict timeout)a)
+ |> Map.put_new(:timeout, @timeout)
+ |> Map.put(:timestamps, timestamps)
+
+ Multi.run(multi, :insert_zkevm_transaction_batches, fn repo, _ ->
+ Instrumenter.block_import_stage_runner(
+ fn -> insert(repo, changes_list, insert_options) end,
+ :block_referencing,
+ :zkevm_transaction_batches,
+ :zkevm_transaction_batches
+ )
+ end)
+ end
+
+ @impl Import.Runner
+ def timeout, do: @timeout
+
+ @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) ::
+ {:ok, [TransactionBatch.t()]}
+ | {:error, [Changeset.t()]}
+ def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do
+ on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
+
+ # Enforce Zkevm.TransactionBatch ShareLocks order (see docs: sharelock.md)
+ ordered_changes_list = Enum.sort_by(changes_list, & &1.number)
+
+ {:ok, inserted} =
+ Import.insert_changes_list(
+ repo,
+ ordered_changes_list,
+ for: TransactionBatch,
+ returning: true,
+ timeout: timeout,
+ timestamps: timestamps,
+ conflict_target: :number,
+ on_conflict: on_conflict
+ )
+
+ {:ok, inserted}
+ end
+
+ defp default_on_conflict do
+ from(
+ tb in TransactionBatch,
+ update: [
+ set: [
+ # don't update `number` as it is a primary key and used for the conflict target
+ timestamp: fragment("EXCLUDED.timestamp"),
+ l2_transactions_count: fragment("EXCLUDED.l2_transactions_count"),
+ global_exit_root: fragment("EXCLUDED.global_exit_root"),
+ acc_input_hash: fragment("EXCLUDED.acc_input_hash"),
+ state_root: fragment("EXCLUDED.state_root"),
+ sequence_id: fragment("EXCLUDED.sequence_id"),
+ verify_id: fragment("EXCLUDED.verify_id"),
+ inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", tb.inserted_at),
+ updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", tb.updated_at)
+ ]
+ ],
+ where:
+ fragment(
+ "(EXCLUDED.timestamp, EXCLUDED.l2_transactions_count, EXCLUDED.global_exit_root, EXCLUDED.acc_input_hash, EXCLUDED.state_root, EXCLUDED.sequence_id, EXCLUDED.verify_id) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?)",
+ tb.timestamp,
+ tb.l2_transactions_count,
+ tb.global_exit_root,
+ tb.acc_input_hash,
+ tb.state_root,
+ tb.sequence_id,
+ tb.verify_id
+ )
+ )
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex
deleted file mode 100644
index 2d40ad74fb47..000000000000
--- a/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex
+++ /dev/null
@@ -1,26 +0,0 @@
-defmodule Explorer.Chain.Import.Stage.AddressReferencing do
- @moduledoc """
- Imports any tables that reference `t:Explorer.Chain.Address.t/0` and that were imported by
- `Explorer.Chain.Import.Stage.Addresses`.
- """
-
- alias Explorer.Chain.Import.{Runner, Stage}
-
- @behaviour Stage
-
- @impl Stage
- def runners,
- do: [
- Runner.Address.CoinBalances,
- Runner.Blocks,
- Runner.Address.CoinBalancesDaily
- ]
-
- @impl Stage
- def multis(runner_to_changes_list, options) do
- {final_multi, final_remaining_runner_to_changes_list} =
- Stage.single_multi(runners(), runner_to_changes_list, options)
-
- {[final_multi], final_remaining_runner_to_changes_list}
- end
-end
diff --git a/apps/explorer/lib/explorer/chain/import/stage/addresses.ex b/apps/explorer/lib/explorer/chain/import/stage/addresses.ex
deleted file mode 100644
index 03c8a5772449..000000000000
--- a/apps/explorer/lib/explorer/chain/import/stage/addresses.ex
+++ /dev/null
@@ -1,22 +0,0 @@
-defmodule Explorer.Chain.Import.Stage.Addresses do
- @moduledoc """
- Imports addresses before anything else that references them because an unused address is still valid and recoverable
- if the other stage(s) don't commit.
- """
-
- alias Explorer.Chain.Import.{Runner, Stage}
-
- @behaviour Stage
-
- @runner Runner.Addresses
-
- @impl Stage
- def runners, do: [@runner]
-
- @chunk_size 50
-
- @impl Stage
- def multis(runner_to_changes_list, options) do
- Stage.chunk_every(runner_to_changes_list, @runner, @chunk_size, options)
- end
-end
diff --git a/apps/explorer/lib/explorer/chain/import/stage/addresses_blocks_coin_balances.ex b/apps/explorer/lib/explorer/chain/import/stage/addresses_blocks_coin_balances.ex
new file mode 100644
index 000000000000..bdd8ae478e84
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/import/stage/addresses_blocks_coin_balances.ex
@@ -0,0 +1,34 @@
+defmodule Explorer.Chain.Import.Stage.AddressesBlocksCoinBalances do
+ @moduledoc """
+ Import addresses, blocks and balances.
+ No tables have foreign key to addresses anymore, so it's possible to import addresses along with them.
+ """
+
+ alias Explorer.Chain.Import.{Runner, Stage}
+
+ @behaviour Stage
+
+ @addresses_runner Runner.Addresses
+
+ @rest_runners [
+ Runner.Address.CoinBalances,
+ Runner.Blocks,
+ Runner.Address.CoinBalancesDaily
+ ]
+
+ @impl Stage
+ def runners, do: [@addresses_runner | @rest_runners]
+
+ @addresses_chunk_size 50
+
+ @impl Stage
+ def multis(runner_to_changes_list, options) do
+ {addresses_multis, remaining_runner_to_changes_list} =
+ Stage.chunk_every(runner_to_changes_list, Runner.Addresses, @addresses_chunk_size, options)
+
+ {final_multi, final_remaining_runner_to_changes_list} =
+ Stage.single_multi(@rest_runners, remaining_runner_to_changes_list, options)
+
+ {[final_multi | addresses_multis], final_remaining_runner_to_changes_list}
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_following.ex b/apps/explorer/lib/explorer/chain/import/stage/block_following.ex
index 65a7c076039c..2a533c1b680b 100644
--- a/apps/explorer/lib/explorer/chain/import/stage/block_following.ex
+++ b/apps/explorer/lib/explorer/chain/import/stage/block_following.ex
@@ -1,9 +1,7 @@
defmodule Explorer.Chain.Import.Stage.BlockFollowing do
@moduledoc """
Imports any tables that follows and cannot be imported at the same time as
- those imported by `Explorer.Chain.Import.Stage.Addresses`,
- `Explorer.Chain.Import.Stage.AddressReferencing` and
- `Explorer.Chain.Import.Stage.BlockReferencing`
+ those imported by `Explorer.Chain.Import.Stage.AddressesBlocksCoinBalances` and `Explorer.Chain.Import.Stage.BlockReferencing`
"""
alias Explorer.Chain.Import.{Runner, Stage}
@@ -15,7 +13,8 @@ defmodule Explorer.Chain.Import.Stage.BlockFollowing do
do: [
Runner.Block.SecondDegreeRelations,
Runner.Block.Rewards,
- Runner.Address.CurrentTokenBalances
+ Runner.Address.CurrentTokenBalances,
+ Runner.TokenInstances
]
@impl Stage
diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_pending.ex b/apps/explorer/lib/explorer/chain/import/stage/block_pending.ex
index fba315e142d4..824a4dc3ce0a 100644
--- a/apps/explorer/lib/explorer/chain/import/stage/block_pending.ex
+++ b/apps/explorer/lib/explorer/chain/import/stage/block_pending.ex
@@ -2,9 +2,7 @@ defmodule Explorer.Chain.Import.Stage.BlockPending do
@moduledoc """
Imports any tables that uses `Explorer.Chain.PendingBlockOperation` to track
progress and cannot be imported at the same time as those imported by
- `Explorer.Chain.Import.Stage.Addresses`,
- `Explorer.Chain.Import.Stage.AddressReferencing` and
- `Explorer.Chain.Import.Stage.BlockReferencing`
+ `Explorer.Chain.Import.Stage.AddressesBlocksCoinBalances` and `Explorer.Chain.Import.Stage.BlockReferencing`
"""
alias Explorer.Chain.Import.{Runner, Stage}
diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex
index 578e4b98942d..142a2326545a 100644
--- a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex
+++ b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex
@@ -1,26 +1,47 @@
defmodule Explorer.Chain.Import.Stage.BlockReferencing do
@moduledoc """
Imports any tables that reference `t:Explorer.Chain.Block.t/0` and that were
- imported by `Explorer.Chain.Import.Stage.Addresses` and
- `Explorer.Chain.Import.Stage.AddressReferencing`.
+ imported by `Explorer.Chain.Import.Stage.AddressesBlocksCoinBalances`.
"""
alias Explorer.Chain.Import.{Runner, Stage}
@behaviour Stage
+ @default_runners [
+ Runner.Transactions,
+ Runner.Transaction.Forks,
+ Runner.Logs,
+ Runner.Tokens,
+ Runner.TokenTransfers,
+ Runner.Address.TokenBalances,
+ Runner.TransactionActions,
+ Runner.Withdrawals
+ ]
@impl Stage
- def runners,
- do: [
- Runner.Transactions,
- Runner.Transaction.Forks,
- Runner.Logs,
- Runner.Tokens,
- Runner.TokenTransfers,
- Runner.Address.TokenBalances,
- Runner.TransactionActions,
- Runner.Withdrawals
- ]
+ def runners do
+ case System.get_env("CHAIN_TYPE") do
+ "polygon_edge" ->
+ @default_runners ++
+ [
+ Runner.PolygonEdge.Deposits,
+ Runner.PolygonEdge.DepositExecutes,
+ Runner.PolygonEdge.Withdrawals,
+ Runner.PolygonEdge.WithdrawalExits
+ ]
+
+ "polygon_zkevm" ->
+ @default_runners ++
+ [
+ Runner.Zkevm.LifecycleTransactions,
+ Runner.Zkevm.TransactionBatches,
+ Runner.Zkevm.BatchTransactions
+ ]
+
+ _ ->
+ @default_runners
+ end
+ end
@impl Stage
def multis(runner_to_changes_list, options) do
diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex
index 6d424c381f8e..82ce66e8c3dc 100644
--- a/apps/explorer/lib/explorer/chain/internal_transaction.ex
+++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex
@@ -444,8 +444,6 @@ defmodule Explorer.Chain.InternalTransaction do
|> validate_call_error_or_result()
|> check_constraint(:call_type, message: ~S|can't be blank when type is 'call'|, name: :call_has_call_type)
|> check_constraint(:input, message: ~S|can't be blank when type is 'call'|, name: :call_has_call_type)
- |> foreign_key_constraint(:from_address_hash)
- |> foreign_key_constraint(:to_address_hash)
|> foreign_key_constraint(:transaction_hash)
|> unique_constraint(:index)
end
@@ -460,8 +458,6 @@ defmodule Explorer.Chain.InternalTransaction do
|> validate_required(@create_required_fields)
|> validate_create_error_or_result()
|> check_constraint(:init, message: ~S|can't be blank when type is 'create'|, name: :create_has_init)
- |> foreign_key_constraint(:created_contract_address_hash)
- |> foreign_key_constraint(:from_address_hash)
|> foreign_key_constraint(:transaction_hash)
|> unique_constraint(:index)
end
@@ -474,8 +470,17 @@ defmodule Explorer.Chain.InternalTransaction do
changeset
|> cast(attrs, @selfdestruct_allowed_fields)
|> validate_required(@selfdestruct_required_fields)
- |> foreign_key_constraint(:from_address_hash)
- |> foreign_key_constraint(:to_address_hash)
+ |> unique_constraint(:index)
+ end
+
+ @stop_optional_fields ~w(from_address_hash gas gas_used error)a
+ @stop_required_fields ~w(block_number transaction_hash transaction_index index type value trace_address)a
+ @stop_allowed_fields @stop_optional_fields ++ @stop_required_fields
+
+ defp type_changeset(changeset, attrs, :stop) do
+ changeset
+ |> cast(attrs, @stop_allowed_fields)
+ |> validate_required(@stop_required_fields)
|> unique_constraint(:index)
end
@@ -542,15 +547,6 @@ defmodule Explorer.Chain.InternalTransaction do
where(query, [t], t.from_address_hash == ^address_hash)
end
- def where_address_fields_match(query, address_hash, nil) do
- where(
- query,
- [it],
- it.to_address_hash == ^address_hash or it.from_address_hash == ^address_hash or
- it.created_contract_address_hash == ^address_hash
- )
- end
-
def where_address_fields_match(query, address_hash, :to_address_hash) do
where(query, [it], it.to_address_hash == ^address_hash)
end
@@ -563,43 +559,24 @@ defmodule Explorer.Chain.InternalTransaction do
where(query, [it], it.created_contract_address_hash == ^address_hash)
end
- def where_is_different_from_parent_transaction(query) do
- where(
- query,
- [it],
- (it.type == ^:call and it.index > 0) or it.type != ^:call
- )
+ def where_address_fields_match(query, address_hash, _) do
+ base_address_where(query, address_hash)
end
- def where_block_number_in_period(query, from_number, to_number) when is_nil(from_number) and not is_nil(to_number) do
+ defp base_address_where(query, address_hash) do
where(
query,
[it],
- it.block_number <= ^to_number
- )
- end
-
- def where_block_number_in_period(query, from_number, to_number) when not is_nil(from_number) and is_nil(to_number) do
- where(
- query,
- [it],
- it.block_number > ^from_number
- )
- end
-
- def where_block_number_in_period(query, from_number, to_number) when is_nil(from_number) and is_nil(to_number) do
- where(
- query,
- [it],
- 1
+ it.to_address_hash == ^address_hash or it.from_address_hash == ^address_hash or
+ it.created_contract_address_hash == ^address_hash
)
end
- def where_block_number_in_period(query, from_number, to_number) do
+ def where_is_different_from_parent_transaction(query) do
where(
query,
[it],
- it.block_number > ^from_number and it.block_number <= ^to_number
+ (it.type == ^:call and it.index > 0) or it.type != ^:call
)
end
diff --git a/apps/explorer/lib/explorer/chain/internal_transaction/type.ex b/apps/explorer/lib/explorer/chain/internal_transaction/type.ex
index 6c2890c59be0..d852ea8ecfdf 100644
--- a/apps/explorer/lib/explorer/chain/internal_transaction/type.ex
+++ b/apps/explorer/lib/explorer/chain/internal_transaction/type.ex
@@ -10,8 +10,9 @@ defmodule Explorer.Chain.InternalTransaction.Type do
* `:create`
* `:reward`
* `:selfdestruct`
+ * `:stop`
"""
- @type t :: :call | :create | :create2 | :reward | :selfdestruct
+ @type t :: :call | :create | :create2 | :reward | :selfdestruct | :stop
@doc """
Casts `term` to `t:t/0`
@@ -63,6 +64,7 @@ defmodule Explorer.Chain.InternalTransaction.Type do
def cast("create2"), do: {:ok, :create2}
def cast("reward"), do: {:ok, :reward}
def cast("selfdestruct"), do: {:ok, :selfdestruct}
+ def cast("stop"), do: {:ok, :stop}
def cast(_), do: :error
@doc """
@@ -97,6 +99,7 @@ defmodule Explorer.Chain.InternalTransaction.Type do
def dump(:create2), do: {:ok, "create2"}
def dump(:reward), do: {:ok, "reward"}
def dump(:selfdestruct), do: {:ok, "selfdestruct"}
+ def dump(:stop), do: {:ok, "stop"}
def dump(_), do: :error
@doc """
@@ -131,6 +134,7 @@ defmodule Explorer.Chain.InternalTransaction.Type do
def load("create2"), do: {:ok, :create2}
def load("reward"), do: {:ok, :reward}
def load("selfdestruct"), do: {:ok, :selfdestruct}
+ def load("stop"), do: {:ok, :stop}
# deprecated
def load("suicide"), do: {:ok, :selfdestruct}
def load(_), do: :error
diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex
index 8524d7c89a4b..be27eead7641 100644
--- a/apps/explorer/lib/explorer/chain/log.ex
+++ b/apps/explorer/lib/explorer/chain/log.ex
@@ -7,11 +7,12 @@ defmodule Explorer.Chain.Log do
alias ABI.{Event, FunctionSelector}
alias Explorer.Chain
- alias Explorer.Chain.{Address, Block, ContractMethod, Data, Hash, Transaction}
+ alias Explorer.Chain.{Address, Block, ContractMethod, Data, Hash, Log, Transaction}
+ alias Explorer.Chain.SmartContract.Proxy
alias Explorer.SmartContract.SigProviderInterface
@required_attrs ~w(address_hash data block_hash index transaction_hash)a
- @optional_attrs ~w(first_topic second_topic third_topic fourth_topic type block_number)a
+ @optional_attrs ~w(first_topic second_topic third_topic fourth_topic block_number)a
@typedoc """
* `address` - address of contract that generate the event
@@ -26,7 +27,6 @@ defmodule Explorer.Chain.Log do
* `transaction` - transaction for which `log` is
* `transaction_hash` - foreign key for `transaction`.
* `index` - index of the log entry in all logs for the `transaction`
- * `type` - type of event. *Nethermind-only*
"""
@type t :: %__MODULE__{
address: %Ecto.Association.NotLoaded{} | Address.t(),
@@ -34,25 +34,23 @@ defmodule Explorer.Chain.Log do
block_hash: Hash.Full.t(),
block_number: non_neg_integer() | nil,
data: Data.t(),
- first_topic: String.t(),
- second_topic: String.t(),
- third_topic: String.t(),
- fourth_topic: String.t(),
+ first_topic: Hash.Full.t(),
+ second_topic: Hash.Full.t(),
+ third_topic: Hash.Full.t(),
+ fourth_topic: Hash.Full.t(),
transaction: %Ecto.Association.NotLoaded{} | Transaction.t(),
transaction_hash: Hash.Full.t(),
- index: non_neg_integer(),
- type: String.t() | nil
+ index: non_neg_integer()
}
@primary_key false
schema "logs" do
field(:data, Data)
- field(:first_topic, :string)
- field(:second_topic, :string)
- field(:third_topic, :string)
- field(:fourth_topic, :string)
+ field(:first_topic, Hash.Full)
+ field(:second_topic, Hash.Full)
+ field(:third_topic, Hash.Full)
+ field(:fourth_topic, Hash.Full)
field(:index, :integer, primary_key: true)
- field(:type, :string)
field(:block_number, :integer)
timestamps()
@@ -75,8 +73,7 @@ defmodule Explorer.Chain.Log do
end
@doc """
- `address_hash` and `transaction_hash` are converted to `t:Explorer.Chain.Hash.t/0`. The allowed values for `type`
- are currently unknown, so it is left as a `t:String.t/0`.
+ `address_hash` and `transaction_hash` are converted to `t:Explorer.Chain.Hash.t/0`.
iex> changeset = Explorer.Chain.Log.changeset(
...> %Explorer.Chain.Log{},
@@ -89,8 +86,7 @@ defmodule Explorer.Chain.Log do
...> index: 0,
...> second_topic: nil,
...> third_topic: nil,
- ...> transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
- ...> type: "mined"
+ ...> transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5"
...> }
...> )
iex> changeset.valid?
@@ -106,8 +102,6 @@ defmodule Explorer.Chain.Log do
bytes: <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, 101, 36,
140, 57, 254, 153, 47, 255, 212, 51, 229>>
}
- iex> changeset.changes.type
- "mined"
"""
def changeset(%__MODULE__{} = log, attrs \\ %{}) do
@@ -120,8 +114,52 @@ defmodule Explorer.Chain.Log do
@doc """
Decode transaction log data.
"""
+ @spec decode(Log.t(), Transaction.t(), any(), boolean, map(), map()) ::
+ {{:ok, String.t(), String.t(), map()}
+ | {:error, atom()}
+ | {:error, atom(), list()}
+ | {{:error, :contract_not_verified, list()}, any()}, map(), map()}
+ def decode(log, transaction, options, skip_sig_provider?, contracts_acc \\ %{}, events_acc \\ %{}) do
+ with {full_abi, contracts_acc} <- check_cache(contracts_acc, log.address_hash, options),
+ {:no_abi, false} <- {:no_abi, is_nil(full_abi)},
+ {:ok, selector, mapping} <- find_and_decode(full_abi, log, transaction.hash),
+ identifier <- Base.encode16(selector.method_id, case: :lower),
+ text <- function_call(selector.function, mapping) do
+ {{:ok, identifier, text, mapping}, contracts_acc, events_acc}
+ else
+ {:error, _} = error ->
+ handle_method_decode_error(error, log, transaction, options, skip_sig_provider?, contracts_acc, events_acc)
+
+ {:no_abi, true} ->
+ handle_method_decode_error(
+ {:error, :could_not_decode},
+ log,
+ transaction,
+ options,
+ skip_sig_provider?,
+ contracts_acc,
+ events_acc
+ )
+ end
+ end
+
+ defp handle_method_decode_error(error, log, transaction, options, skip_sig_provider?, contracts_acc, events_acc) do
+ case error do
+ {:error, _reason} ->
+ case find_method_candidates(log, transaction, options, events_acc, skip_sig_provider?) do
+ {{:error, :contract_not_verified, []}, events_acc} ->
+ {decode_event_via_sig_provider(log, transaction, false, skip_sig_provider?), contracts_acc, events_acc}
- def decode(log, transaction, options \\ [], skip_sig_provider? \\ false) do
+ {{:error, :contract_not_verified, candidates}, events_acc} ->
+ {{:error, :contract_not_verified, candidates}, contracts_acc, events_acc}
+
+ {_, events_acc} ->
+ {decode_event_via_sig_provider(log, transaction, false, skip_sig_provider?), contracts_acc, events_acc}
+ end
+ end
+ end
+
+ defp check_cache(acc, address_hash, options) do
address_options =
[
necessity_by_association: %{
@@ -130,66 +168,43 @@ defmodule Explorer.Chain.Log do
]
|> Keyword.merge(options)
- case Chain.find_contract_address(log.address_hash, address_options, true) do
- {:ok, %{smart_contract: smart_contract}} ->
- full_abi = Chain.combine_proxy_implementation_abi(smart_contract, options)
-
- with {:ok, selector, mapping} <- find_and_decode(full_abi, log, transaction),
- identifier <- Base.encode16(selector.method_id, case: :lower),
- text <- function_call(selector.function, mapping) do
- {:ok, identifier, text, mapping}
- else
- {:error, :could_not_decode} ->
- case find_candidates(log, transaction, options) do
- {:error, :contract_not_verified, []} ->
- decode_event_via_sig_provider(log, transaction, false, skip_sig_provider?)
-
- {:error, :contract_not_verified, candidates} ->
- {:error, :contract_verified, candidates}
-
- _ ->
- decode_event_via_sig_provider(log, transaction, false, skip_sig_provider?)
- end
-
- output ->
- output
- end
+ if !is_nil(address_hash) && Map.has_key?(acc, address_hash) do
+ {acc[address_hash], acc}
+ else
+ case Chain.find_contract_address(address_hash, address_options, false) do
+ {:ok, %{smart_contract: smart_contract}} ->
+ full_abi = Proxy.combine_proxy_implementation_abi(smart_contract, options)
+ {full_abi, Map.put(acc, address_hash, full_abi)}
- _ ->
- find_candidates(log, transaction, options)
+ _ ->
+ {nil, Map.put(acc, address_hash, nil)}
+ end
end
end
- defp find_candidates(log, transaction, options) do
- case log.first_topic do
- "0x" <> hex_part ->
- case Integer.parse(hex_part, 16) do
- {number, ""} ->
- <> = :binary.encode_unsigned(number)
- find_candidates_query(method_id, log, transaction, options)
-
- _ ->
- {:error, :could_not_decode}
- end
+ defp find_method_candidates(log, transaction, options, events_acc, skip_sig_provider?) do
+ if is_nil(log.first_topic) do
+ {{:error, :could_not_decode}, events_acc}
+ else
+ <> = log.first_topic.bytes
- _ ->
- {:error, :could_not_decode}
+ if Map.has_key?(events_acc, method_id) do
+ {events_acc[method_id], events_acc}
+ else
+ result = find_method_candidates_from_db(method_id, log, transaction, options, skip_sig_provider?)
+ {result, Map.put(events_acc, method_id, result)}
+ end
end
end
- defp find_candidates_query(method_id, log, transaction, options) do
- candidates_query =
- from(
- contract_method in ContractMethod,
- where: contract_method.identifier == ^method_id,
- limit: 3
- )
+ defp find_method_candidates_from_db(method_id, log, transaction, options, skip_sig_provider?) do
+ candidates_query = ContractMethod.find_contract_method_query(method_id, 3)
candidates =
candidates_query
|> Chain.select_repo(options).all()
|> Enum.flat_map(fn contract_method ->
- case find_and_decode([contract_method.abi], log, transaction) do
+ case find_and_decode([contract_method.abi], log, transaction.hash) do
{:ok, selector, mapping} ->
identifier = Base.encode16(selector.method_id, case: :lower)
text = function_call(selector.function, mapping)
@@ -203,18 +218,27 @@ defmodule Explorer.Chain.Log do
|> Enum.take(1)
{:error, :contract_not_verified,
- if(candidates == [], do: decode_event_via_sig_provider(log, transaction, true), else: candidates)}
+ if(candidates == [],
+ do:
+ if(skip_sig_provider?,
+ do: [],
+ else: decode_event_via_sig_provider(log, transaction, true)
+ ),
+ else: candidates
+ )}
end
- defp find_and_decode(abi, log, transaction) do
+ @spec find_and_decode([map()], __MODULE__.t(), Hash.t()) ::
+ {:error, any} | {:ok, ABI.FunctionSelector.t(), any}
+ def find_and_decode(abi, log, transaction_hash) do
with {%FunctionSelector{} = selector, mapping} <-
abi
|> ABI.parse_specification(include_events?: true)
|> Event.find_and_decode(
- decode16!(log.first_topic),
- decode16!(log.second_topic),
- decode16!(log.third_topic),
- decode16!(log.fourth_topic),
+ log.first_topic && log.first_topic.bytes,
+ log.second_topic && log.second_topic.bytes,
+ log.third_topic && log.third_topic.bytes,
+ log.fourth_topic && log.fourth_topic.bytes,
log.data.bytes
) do
{:ok, selector, mapping}
@@ -223,8 +247,8 @@ defmodule Explorer.Chain.Log do
e ->
Logger.warn(fn ->
[
- "Could not decode input data for log from transaction: ",
- Hash.to_iodata(transaction.hash),
+ "Could not decode input data for log from transaction hash: ",
+ Hash.to_iodata(transaction_hash),
Exception.format(:error, e, __STACKTRACE__)
]
end)
@@ -236,12 +260,7 @@ defmodule Explorer.Chain.Log do
text =
mapping
|> Stream.map(fn {name, type, indexed?, _value} ->
- indexed_keyword =
- if indexed? do
- ["indexed "]
- else
- []
- end
+ indexed_keyword = if indexed?, do: ["indexed "], else: []
[type, " ", indexed_keyword, name]
end)
@@ -250,7 +269,12 @@ defmodule Explorer.Chain.Log do
IO.iodata_to_binary([name, "(", text, ")"])
end
- defp decode_event_via_sig_provider(log, transaction, only_candidates?, skip_sig_provider? \\ false) do
+ defp decode_event_via_sig_provider(
+ log,
+ transaction,
+ only_candidates?,
+ skip_sig_provider? \\ false
+ ) do
with true <- SigProviderInterface.enabled?(),
false <- skip_sig_provider?,
{:ok, result} <-
@@ -266,7 +290,7 @@ defmodule Explorer.Chain.Log do
true <- is_list(result),
false <- Enum.empty?(result),
abi <- [result |> List.first() |> Map.put("type", "event")],
- {:ok, selector, mapping} <- find_and_decode(abi, log, transaction),
+ {:ok, selector, mapping} <- find_and_decode(abi, log, transaction.hash),
identifier <- Base.encode16(selector.method_id, case: :lower),
text <- function_call(selector.function, mapping) do
if only_candidates? do
@@ -291,4 +315,11 @@ defmodule Explorer.Chain.Log do
|> String.trim_leading("0x")
|> Base.decode16!(case: :lower)
end
+
+ def fetch_log_by_tx_hash_and_first_topic(tx_hash, first_topic, options \\ []) do
+ __MODULE__
+ |> where([l], l.transaction_hash == ^tx_hash and l.first_topic == ^first_topic)
+ |> limit(1)
+ |> Chain.select_repo(options).one()
+ end
end
diff --git a/apps/explorer/lib/explorer/chain/polygon_edge/deposit.ex b/apps/explorer/lib/explorer/chain/polygon_edge/deposit.ex
new file mode 100644
index 000000000000..b9ad75bc3a5b
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/polygon_edge/deposit.ex
@@ -0,0 +1,53 @@
+defmodule Explorer.Chain.PolygonEdge.Deposit do
+ @moduledoc "Models Polygon Edge deposit."
+
+ use Explorer.Schema
+
+ alias Explorer.Chain.{
+ Block,
+ Hash
+ }
+
+ @optional_attrs ~w(from to l1_transaction_hash l1_timestamp)a
+
+ @required_attrs ~w(msg_id l1_block_number)a
+
+ @allowed_attrs @optional_attrs ++ @required_attrs
+
+ @typedoc """
+ * `msg_id` - id of the message
+ * `from` - source address of the message
+ * `to` - target address of the message
+ * `l1_transaction_hash` - hash of the L1 transaction containing the corresponding StateSynced event
+ * `l1_timestamp` - timestamp of the L1 transaction block
+ * `l1_block_number` - block number of the L1 transaction
+ """
+ @type t :: %__MODULE__{
+ msg_id: non_neg_integer(),
+ from: Hash.Address.t() | nil,
+ to: Hash.Address.t() | nil,
+ l1_transaction_hash: Hash.t() | nil,
+ l1_timestamp: DateTime.t() | nil,
+ l1_block_number: Block.block_number()
+ }
+
+ @primary_key false
+ schema "polygon_edge_deposits" do
+ field(:msg_id, :integer, primary_key: true)
+ field(:from, Hash.Address)
+ field(:to, Hash.Address)
+ field(:l1_transaction_hash, Hash.Full)
+ field(:l1_timestamp, :utc_datetime_usec)
+ field(:l1_block_number, :integer)
+
+ timestamps()
+ end
+
+ @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t()
+ def changeset(%__MODULE__{} = module, attrs \\ %{}) do
+ module
+ |> cast(attrs, @allowed_attrs)
+ |> validate_required(@required_attrs)
+ |> unique_constraint(:msg_id)
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/polygon_edge/deposit_execute.ex b/apps/explorer/lib/explorer/chain/polygon_edge/deposit_execute.ex
new file mode 100644
index 000000000000..e3e7617d579f
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/polygon_edge/deposit_execute.ex
@@ -0,0 +1,41 @@
+defmodule Explorer.Chain.PolygonEdge.DepositExecute do
+ @moduledoc "Models Polygon Edge deposit execute."
+
+ use Explorer.Schema
+
+ alias Explorer.Chain.{Block, Hash}
+
+ @required_attrs ~w(msg_id l2_transaction_hash l2_block_number success)a
+
+ @typedoc """
+ * `msg_id` - id of the message
+ * `l2_transaction_hash` - hash of the L2 transaction containing the corresponding StateSyncResult event
+ * `l2_block_number` - block number of the L2 transaction
+ * `success` - a status of onStateReceive internal call (namely internal deposit transaction)
+ """
+ @type t :: %__MODULE__{
+ msg_id: non_neg_integer(),
+ l2_transaction_hash: Hash.t(),
+ l2_block_number: Block.block_number(),
+ success: boolean()
+ }
+
+ @primary_key false
+ schema "polygon_edge_deposit_executes" do
+ field(:msg_id, :integer, primary_key: true)
+ field(:l2_transaction_hash, Hash.Full)
+ field(:l2_block_number, :integer)
+ field(:success, :boolean)
+
+ timestamps()
+ end
+
+ @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t()
+ def changeset(%__MODULE__{} = module, attrs \\ %{}) do
+ module
+ |> cast(attrs, @required_attrs)
+ |> validate_required(@required_attrs)
+ |> unique_constraint(:msg_id)
+ |> unique_constraint(:l2_transaction_hash)
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/polygon_edge/reader.ex b/apps/explorer/lib/explorer/chain/polygon_edge/reader.ex
new file mode 100644
index 000000000000..9d8d7dbf4bb5
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/polygon_edge/reader.ex
@@ -0,0 +1,141 @@
+defmodule Explorer.Chain.PolygonEdge.Reader do
+ @moduledoc "Contains read functions for Polygon Edge modules."
+
+ import Ecto.Query,
+ only: [
+ from: 2,
+ limit: 2
+ ]
+
+ import Explorer.Chain, only: [default_paging_options: 0, select_repo: 1]
+
+ alias Explorer.{PagingOptions, Repo}
+ alias Explorer.Chain.PolygonEdge.{Deposit, DepositExecute, Withdrawal, WithdrawalExit}
+ alias Explorer.Chain.{Block, Hash}
+
+ @spec deposits(list()) :: list()
+ def deposits(options \\ []) do
+ paging_options = Keyword.get(options, :paging_options, default_paging_options())
+
+ base_query =
+ from(
+ de in DepositExecute,
+ inner_join: d in Deposit,
+ on: d.msg_id == de.msg_id and not is_nil(d.l1_timestamp),
+ select: %{
+ msg_id: de.msg_id,
+ from: d.from,
+ to: d.to,
+ l1_transaction_hash: d.l1_transaction_hash,
+ l1_timestamp: d.l1_timestamp,
+ success: de.success,
+ l2_transaction_hash: de.l2_transaction_hash
+ },
+ order_by: [desc: de.msg_id]
+ )
+
+ base_query
+ |> page_deposits_or_withdrawals(paging_options)
+ |> limit(^paging_options.page_size)
+ |> select_repo(options).all()
+ end
+
+ @spec deposits_count(list()) :: term() | nil
+ def deposits_count(options \\ []) do
+ query =
+ from(
+ de in DepositExecute,
+ inner_join: d in Deposit,
+ on: d.msg_id == de.msg_id and not is_nil(d.l1_timestamp)
+ )
+
+ select_repo(options).aggregate(query, :count, timeout: :infinity)
+ end
+
+ @spec withdrawals(list()) :: list()
+ def withdrawals(options \\ []) do
+ paging_options = Keyword.get(options, :paging_options, default_paging_options())
+
+ base_query =
+ from(
+ w in Withdrawal,
+ left_join: we in WithdrawalExit,
+ on: we.msg_id == w.msg_id,
+ left_join: b in Block,
+ on: b.number == w.l2_block_number and b.consensus == true,
+ select: %{
+ msg_id: w.msg_id,
+ from: w.from,
+ to: w.to,
+ l2_transaction_hash: w.l2_transaction_hash,
+ l2_timestamp: b.timestamp,
+ success: we.success,
+ l1_transaction_hash: we.l1_transaction_hash
+ },
+ where: not is_nil(w.from),
+ order_by: [desc: w.msg_id]
+ )
+
+ base_query
+ |> page_deposits_or_withdrawals(paging_options)
+ |> limit(^paging_options.page_size)
+ |> select_repo(options).all()
+ end
+
+ @spec withdrawals_count(list()) :: term() | nil
+ def withdrawals_count(options \\ []) do
+ query =
+ from(
+ w in Withdrawal,
+ where: not is_nil(w.from)
+ )
+
+ select_repo(options).aggregate(query, :count, timeout: :infinity)
+ end
+
+ @spec deposit_by_transaction_hash(Hash.t()) :: Ecto.Schema.t() | term() | nil
+ def deposit_by_transaction_hash(hash) do
+ query =
+ from(
+ de in DepositExecute,
+ inner_join: d in Deposit,
+ on: d.msg_id == de.msg_id and not is_nil(d.from),
+ select: %{
+ msg_id: de.msg_id,
+ from: d.from,
+ to: d.to,
+ success: de.success,
+ l1_transaction_hash: d.l1_transaction_hash
+ },
+ where: de.l2_transaction_hash == ^hash
+ )
+
+ Repo.replica().one(query)
+ end
+
+ @spec withdrawal_by_transaction_hash(Hash.t()) :: Ecto.Schema.t() | term() | nil
+ def withdrawal_by_transaction_hash(hash) do
+ query =
+ from(
+ w in Withdrawal,
+ left_join: we in WithdrawalExit,
+ on: we.msg_id == w.msg_id,
+ select: %{
+ msg_id: w.msg_id,
+ from: w.from,
+ to: w.to,
+ success: we.success,
+ l1_transaction_hash: we.l1_transaction_hash
+ },
+ where: w.l2_transaction_hash == ^hash and not is_nil(w.from)
+ )
+
+ Repo.replica().one(query)
+ end
+
+ defp page_deposits_or_withdrawals(query, %PagingOptions{key: nil}), do: query
+
+ defp page_deposits_or_withdrawals(query, %PagingOptions{key: {msg_id}}) do
+ from(item in query, where: item.msg_id < ^msg_id)
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/polygon_edge/withdrawal.ex b/apps/explorer/lib/explorer/chain/polygon_edge/withdrawal.ex
new file mode 100644
index 000000000000..9cfc109b0bcb
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/polygon_edge/withdrawal.ex
@@ -0,0 +1,58 @@
+defmodule Explorer.Chain.PolygonEdge.Withdrawal do
+ @moduledoc "Models Polygon Edge withdrawal."
+
+ use Explorer.Schema
+
+ alias Explorer.Chain.{
+ Address,
+ Block,
+ Hash,
+ Transaction
+ }
+
+ @optional_attrs ~w(from to)a
+
+ @required_attrs ~w(msg_id l2_transaction_hash l2_block_number)a
+
+ @allowed_attrs @optional_attrs ++ @required_attrs
+
+ @typedoc """
+ * `msg_id` - id of the message
+ * `from` - source address of the message
+ * `to` - target address of the message
+ * `l2_transaction_hash` - hash of the L2 transaction containing the corresponding L2StateSynced event
+ * `l2_block_number` - block number of the L2 transaction
+ """
+ @type t :: %__MODULE__{
+ msg_id: non_neg_integer(),
+ from: Hash.Address.t() | nil,
+ from_address: %Ecto.Association.NotLoaded{} | Address.t() | nil,
+ to: Hash.Address.t() | nil,
+ to_address: %Ecto.Association.NotLoaded{} | Address.t() | nil,
+ l2_transaction_hash: Hash.t(),
+ l2_transaction: %Ecto.Association.NotLoaded{} | Transaction.t(),
+ l2_block_number: Block.block_number(),
+ l2_block: %Ecto.Association.NotLoaded{} | Block.t()
+ }
+
+ @primary_key false
+ schema "polygon_edge_withdrawals" do
+ field(:msg_id, :integer, primary_key: true)
+
+ belongs_to(:from_address, Address, foreign_key: :from, references: :hash, type: Hash.Address)
+ belongs_to(:to_address, Address, foreign_key: :to, references: :hash, type: Hash.Address)
+ belongs_to(:l2_transaction, Transaction, foreign_key: :l2_transaction_hash, references: :hash, type: Hash.Full)
+ belongs_to(:l2_block, Block, foreign_key: :l2_block_number, references: :number, type: :integer)
+
+ timestamps()
+ end
+
+ @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t()
+ def changeset(%__MODULE__{} = module, attrs \\ %{}) do
+ module
+ |> cast(attrs, @allowed_attrs)
+ |> validate_required(@required_attrs)
+ |> unique_constraint(:msg_id)
+ |> unique_constraint(:l2_transaction_hash)
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/polygon_edge/withdrawal_exit.ex b/apps/explorer/lib/explorer/chain/polygon_edge/withdrawal_exit.ex
new file mode 100644
index 000000000000..27ee5583af66
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/polygon_edge/withdrawal_exit.ex
@@ -0,0 +1,40 @@
+defmodule Explorer.Chain.PolygonEdge.WithdrawalExit do
+ @moduledoc "Models Polygon Edge withdrawal exit."
+
+ use Explorer.Schema
+
+ alias Explorer.Chain.{Block, Hash}
+
+ @required_attrs ~w(msg_id l1_transaction_hash l1_block_number success)a
+
+ @typedoc """
+ * `msg_id` - id of the message
+ * `l1_transaction_hash` - hash of the L1 transaction containing the corresponding ExitProcessed event
+ * `l1_block_number` - block number of the L1 transaction
+ * `success` - a status of onL2StateReceive internal call (namely internal withdrawal transaction)
+ """
+ @type t :: %__MODULE__{
+ msg_id: non_neg_integer(),
+ l1_transaction_hash: Hash.t(),
+ l1_block_number: Block.block_number(),
+ success: boolean()
+ }
+
+ @primary_key false
+ schema "polygon_edge_withdrawal_exits" do
+ field(:msg_id, :integer, primary_key: true)
+ field(:l1_transaction_hash, Hash.Full)
+ field(:l1_block_number, :integer)
+ field(:success, :boolean)
+
+ timestamps()
+ end
+
+ @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t()
+ def changeset(%__MODULE__{} = module, attrs \\ %{}) do
+ module
+ |> cast(attrs, @required_attrs)
+ |> validate_required(@required_attrs)
+ |> unique_constraint(:msg_id)
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/search.ex b/apps/explorer/lib/explorer/chain/search.ex
new file mode 100644
index 000000000000..20082bccbfb8
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/search.ex
@@ -0,0 +1,634 @@
+defmodule Explorer.Chain.Search do
+ @moduledoc """
+ Search related functions
+ """
+ import Ecto.Query,
+ only: [
+ dynamic: 2,
+ from: 2,
+ limit: 2,
+ order_by: 3,
+ subquery: 1,
+ union: 2,
+ where: 3
+ ]
+
+ import Explorer.Chain, only: [select_repo: 1]
+ import Explorer.MicroserviceInterfaces.BENS, only: [ens_domain_name_lookup: 1]
+ alias Explorer.{Chain, PagingOptions}
+ alias Explorer.Tags.{AddressTag, AddressToTag}
+
+ alias Explorer.Chain.{
+ Address,
+ Block,
+ DenormalizationHelper,
+ SmartContract,
+ Token,
+ Transaction
+ }
+
+ @doc """
+ Search function used in web interface. Returns paginated search results
+ """
+ @spec joint_search(PagingOptions.t(), integer(), binary(), [Chain.api?()] | []) :: list
+ def joint_search(paging_options, offset, raw_string, options \\ []) do
+ string = String.trim(raw_string)
+
+ ens_task = maybe_run_ens_task(paging_options, raw_string, options)
+
+ result =
+ case prepare_search_term(string) do
+ {:some, term} ->
+ tokens_query = search_token_query(string, term)
+ contracts_query = search_contract_query(term)
+ labels_query = search_label_query(term)
+ tx_query = search_tx_query(string)
+ address_query = search_address_query(string)
+ block_query = search_block_query(string)
+
+ basic_query =
+ from(
+ tokens in subquery(tokens_query),
+ union: ^contracts_query,
+ union: ^labels_query
+ )
+
+ query =
+ cond do
+ address_query ->
+ basic_query
+ |> union(^address_query)
+
+ tx_query ->
+ basic_query
+ |> union(^tx_query)
+ |> union(^block_query)
+
+ block_query ->
+ basic_query
+ |> union(^block_query)
+
+ true ->
+ basic_query
+ end
+
+ ordered_query =
+ from(items in subquery(query),
+ order_by: [
+ desc: items.priority,
+ desc_nulls_last: items.circulating_market_cap,
+ desc_nulls_last: items.exchange_rate,
+ desc_nulls_last: items.is_verified_via_admin_panel,
+ desc_nulls_last: items.holder_count,
+ asc: items.name,
+ desc: items.inserted_at
+ ],
+ limit: ^paging_options.page_size,
+ offset: ^offset
+ )
+
+ paginated_ordered_query =
+ ordered_query
+ |> page_search_results(paging_options)
+
+ search_results = select_repo(options).all(paginated_ordered_query)
+
+ search_results
+ |> Enum.map(fn result ->
+ result
+ |> compose_result_checksummed_address_hash()
+ |> format_timestamp()
+ end)
+
+ _ ->
+ []
+ end
+
+ ens_result = (ens_task && await_ens_task(ens_task)) || []
+
+ result ++ ens_result
+ end
+
+ defp maybe_run_ens_task(%PagingOptions{key: nil}, query_string, options) do
+ Task.async(fn -> search_ens_name(query_string, options) end)
+ end
+
+ defp maybe_run_ens_task(_, _query_string, _options), do: nil
+
+ @doc """
+ Search function. Differences from joint_search/4:
+ 1. Returns all the found categories (amount of results up to `paging_options.page_size`).
+ For example if was found 50 tokens, 50 smart-contracts, 50 labels, 1 address, 1 transaction and 2 blocks (impossible, just example) and page_size=50. Then function will return:
+ [1 address, 1 transaction, 2 blocks, 16 tokens, 15 smart-contracts, 15 labels]
+ 2. Results couldn't be paginated
+ """
+ @spec balanced_unpaginated_search(PagingOptions.t(), binary(), [Chain.api?()] | []) :: list
+ def balanced_unpaginated_search(paging_options, raw_search_query, options \\ []) do
+ search_query = String.trim(raw_search_query)
+ ens_task = Task.async(fn -> search_ens_name(raw_search_query, options) end)
+
+ case prepare_search_term(search_query) do
+ {:some, term} ->
+ tokens_result =
+ search_query
+ |> search_token_query(term)
+ |> order_by([token],
+ desc_nulls_last: token.circulating_market_cap,
+ desc_nulls_last: token.fiat_value,
+ desc_nulls_last: token.is_verified_via_admin_panel,
+ desc_nulls_last: token.holder_count,
+ asc: token.name,
+ desc: token.inserted_at
+ )
+ |> limit(^paging_options.page_size)
+ |> select_repo(options).all()
+
+ contracts_result =
+ term
+ |> search_contract_query()
+ |> order_by([items], asc: items.name, desc: items.inserted_at)
+ |> limit(^paging_options.page_size)
+ |> select_repo(options).all()
+
+ labels_result =
+ term
+ |> search_label_query()
+ |> order_by([att, at], asc: at.display_name, desc: att.inserted_at)
+ |> limit(^paging_options.page_size)
+ |> select_repo(options).all()
+
+ tx_result =
+ if query = search_tx_query(search_query) do
+ query
+ |> select_repo(options).all()
+ else
+ []
+ end
+
+ address_result =
+ if query = search_address_query(search_query) do
+ query
+ |> select_repo(options).all()
+ else
+ []
+ end
+
+ blocks_result =
+ if query = search_block_query(search_query) do
+ query
+ |> limit(^paging_options.page_size)
+ |> select_repo(options).all()
+ else
+ []
+ end
+
+ ens_result = await_ens_task(ens_task)
+
+ non_empty_lists =
+ [tokens_result, contracts_result, labels_result, tx_result, address_result, blocks_result, ens_result]
+ |> Enum.filter(fn list -> Enum.count(list) > 0 end)
+ |> Enum.sort_by(fn list -> Enum.count(list) end, :asc)
+
+ to_take =
+ non_empty_lists
+ |> Enum.map(fn list -> Enum.count(list) end)
+ |> take_all_categories(List.duplicate(0, Enum.count(non_empty_lists)), paging_options.page_size)
+
+ non_empty_lists
+ |> Enum.zip_reduce(to_take, [], fn x, y, acc -> acc ++ Enum.take(x, y) end)
+ |> Enum.map(fn result ->
+ result
+ |> compose_result_checksummed_address_hash()
+ |> format_timestamp()
+ end)
+
+ _ ->
+ []
+ end
+ end
+
+ defp await_ens_task(ens_task) do
+ case Task.yield(ens_task, 5000) || Task.shutdown(ens_task) do
+ {:ok, result} ->
+ result
+
+ _ ->
+ []
+ end
+ end
+
+ def prepare_search_term(string) do
+ case Regex.scan(~r/[a-zA-Z0-9]+/, string) do
+ [_ | _] = words ->
+ term_final =
+ words
+ |> Enum.map_join(" & ", fn [word] -> word <> ":*" end)
+
+ {:some, term_final}
+
+ _ ->
+ :none
+ end
+ end
+
+ defp search_label_query(term) do
+ label_search_fields = %{
+ address_hash: dynamic([att, _, _], att.address_hash),
+ tx_hash: dynamic([_, _, _], type(^nil, :binary)),
+ block_hash: dynamic([_, _, _], type(^nil, :binary)),
+ type: "label",
+ name: dynamic([_, at, _], at.display_name),
+ symbol: nil,
+ holder_count: nil,
+ inserted_at: dynamic([att, _, _], att.inserted_at),
+ block_number: 0,
+ icon_url: nil,
+ token_type: nil,
+ timestamp: dynamic([_, _, _], type(^nil, :utc_datetime_usec)),
+ verified: dynamic([_, _, smart_contract], not is_nil(smart_contract)),
+ exchange_rate: nil,
+ total_supply: nil,
+ circulating_market_cap: nil,
+ priority: 1,
+ is_verified_via_admin_panel: nil
+ }
+
+ inner_query =
+ from(tag in AddressTag,
+ where: fragment("to_tsvector('english', ?) @@ to_tsquery(?)", tag.display_name, ^term),
+ select: tag
+ )
+
+ from(att in AddressToTag,
+ inner_join: at in subquery(inner_query),
+ on: att.tag_id == at.id,
+ left_join: smart_contract in SmartContract,
+ on: att.address_hash == smart_contract.address_hash,
+ select: ^label_search_fields
+ )
+ end
+
+ defp search_token_query(string, term) do
+ token_search_fields = %{
+ address_hash: dynamic([token, _], token.contract_address_hash),
+ tx_hash: dynamic([_, _], type(^nil, :binary)),
+ block_hash: dynamic([_, _], type(^nil, :binary)),
+ type: "token",
+ name: dynamic([token, _], token.name),
+ symbol: dynamic([token, _], token.symbol),
+ holder_count: dynamic([token, _], token.holder_count),
+ inserted_at: dynamic([token, _], token.inserted_at),
+ block_number: 0,
+ icon_url: dynamic([token, _], token.icon_url),
+ token_type: dynamic([token, _], token.type),
+ timestamp: dynamic([_, _], type(^nil, :utc_datetime_usec)),
+ verified: dynamic([_, smart_contract], not is_nil(smart_contract)),
+ exchange_rate: dynamic([token, _], token.fiat_value),
+ total_supply: dynamic([token, _], token.total_supply),
+ circulating_market_cap: dynamic([token, _], token.circulating_market_cap),
+ priority: 0,
+ is_verified_via_admin_panel: dynamic([token, _], token.is_verified_via_admin_panel)
+ }
+
+ case Chain.string_to_address_hash(string) do
+ {:ok, address_hash} ->
+ from(token in Token,
+ left_join: smart_contract in SmartContract,
+ on: token.contract_address_hash == smart_contract.address_hash,
+ where: token.contract_address_hash == ^address_hash,
+ select: ^token_search_fields
+ )
+
+ _ ->
+ from(token in Token,
+ left_join: smart_contract in SmartContract,
+ on: token.contract_address_hash == smart_contract.address_hash,
+ where: fragment("to_tsvector('english', ? || ' ' || ?) @@ to_tsquery(?)", token.symbol, token.name, ^term),
+ select: ^token_search_fields
+ )
+ end
+ end
+
+ defp search_contract_query(term) do
+ contract_search_fields = %{
+ address_hash: dynamic([smart_contract, _], smart_contract.address_hash),
+ tx_hash: dynamic([_, _], type(^nil, :binary)),
+ block_hash: dynamic([_, _], type(^nil, :binary)),
+ type: "contract",
+ name: dynamic([smart_contract, _], smart_contract.name),
+ symbol: nil,
+ holder_count: nil,
+ inserted_at: dynamic([_, address], address.inserted_at),
+ block_number: 0,
+ icon_url: nil,
+ token_type: nil,
+ timestamp: dynamic([_, _], type(^nil, :utc_datetime_usec)),
+ verified: true,
+ exchange_rate: nil,
+ total_supply: nil,
+ circulating_market_cap: nil,
+ priority: 0,
+ is_verified_via_admin_panel: nil
+ }
+
+ from(smart_contract in SmartContract,
+ left_join: address in Address,
+ on: smart_contract.address_hash == address.hash,
+ where: fragment("to_tsvector('english', ?) @@ to_tsquery(?)", smart_contract.name, ^term),
+ select: ^contract_search_fields
+ )
+ end
+
+ defp search_address_query(term) do
+ case Chain.string_to_address_hash(term) do
+ {:ok, address_hash} ->
+ address_search_fields = %{
+ address_hash: dynamic([address, _], address.hash),
+ block_hash: dynamic([_, _], type(^nil, :binary)),
+ tx_hash: dynamic([_, _], type(^nil, :binary)),
+ type: "address",
+ name: dynamic([_, address_name], address_name.name),
+ symbol: nil,
+ holder_count: nil,
+ inserted_at: dynamic([address, _], address.inserted_at),
+ block_number: 0,
+ icon_url: nil,
+ token_type: nil,
+ timestamp: dynamic([_, _], type(^nil, :utc_datetime_usec)),
+ verified: dynamic([address, _], address.verified),
+ exchange_rate: nil,
+ total_supply: nil,
+ circulating_market_cap: nil,
+ priority: 0,
+ is_verified_via_admin_panel: nil
+ }
+
+ from(address in Address,
+ left_join:
+ address_name in subquery(
+ from(name in Address.Name,
+ where: name.address_hash == ^address_hash,
+ order_by: [desc: name.primary],
+ limit: 1
+ )
+ ),
+ on: address.hash == address_name.address_hash,
+ where: address.hash == ^address_hash,
+ select: ^address_search_fields
+ )
+
+ _ ->
+ nil
+ end
+ end
+
+ defp search_tx_query(term) do
+ if DenormalizationHelper.denormalization_finished?() do
+ case Chain.string_to_transaction_hash(term) do
+ {:ok, tx_hash} ->
+ transaction_search_fields = %{
+ address_hash: dynamic([_], type(^nil, :binary)),
+ tx_hash: dynamic([transaction], transaction.hash),
+ block_hash: dynamic([_], type(^nil, :binary)),
+ type: "transaction",
+ name: nil,
+ symbol: nil,
+ holder_count: nil,
+ inserted_at: dynamic([transaction], transaction.inserted_at),
+ block_number: 0,
+ icon_url: nil,
+ token_type: nil,
+ timestamp: dynamic([transaction], transaction.block_timestamp),
+ verified: nil,
+ exchange_rate: nil,
+ total_supply: nil,
+ circulating_market_cap: nil,
+ priority: 0,
+ is_verified_via_admin_panel: nil
+ }
+
+ from(transaction in Transaction,
+ where: transaction.hash == ^tx_hash,
+ select: ^transaction_search_fields
+ )
+
+ _ ->
+ nil
+ end
+ else
+ case Chain.string_to_transaction_hash(term) do
+ {:ok, tx_hash} ->
+ transaction_search_fields = %{
+ address_hash: dynamic([_, _], type(^nil, :binary)),
+ tx_hash: dynamic([transaction, _], transaction.hash),
+ block_hash: dynamic([_, _], type(^nil, :binary)),
+ type: "transaction",
+ name: nil,
+ symbol: nil,
+ holder_count: nil,
+ inserted_at: dynamic([transaction, _], transaction.inserted_at),
+ block_number: 0,
+ icon_url: nil,
+ token_type: nil,
+ timestamp: dynamic([_, block], block.timestamp),
+ verified: nil,
+ exchange_rate: nil,
+ total_supply: nil,
+ circulating_market_cap: nil,
+ priority: 0,
+ is_verified_via_admin_panel: nil
+ }
+
+ from(transaction in Transaction,
+ left_join: block in Block,
+ on: transaction.block_hash == block.hash,
+ where: transaction.hash == ^tx_hash,
+ select: ^transaction_search_fields
+ )
+
+ _ ->
+ nil
+ end
+ end
+ end
+
+ defp search_block_query(term) do
+ block_search_fields = %{
+ address_hash: dynamic([_], type(^nil, :binary)),
+ tx_hash: dynamic([_], type(^nil, :binary)),
+ block_hash: dynamic([block], block.hash),
+ type: "block",
+ name: nil,
+ symbol: nil,
+ holder_count: nil,
+ inserted_at: dynamic([block], block.inserted_at),
+ block_number: dynamic([block], block.number),
+ icon_url: nil,
+ token_type: nil,
+ timestamp: dynamic([block], block.timestamp),
+ verified: nil,
+ exchange_rate: nil,
+ total_supply: nil,
+ circulating_market_cap: nil,
+ priority: 0,
+ is_verified_via_admin_panel: nil
+ }
+
+ case Chain.string_to_block_hash(term) do
+ {:ok, block_hash} ->
+ from(block in Block,
+ where: block.hash == ^block_hash,
+ select: ^block_search_fields
+ )
+
+ _ ->
+ case Integer.parse(term) do
+ {block_number, ""} ->
+ from(block in Block,
+ where: block.number == ^block_number,
+ select: ^block_search_fields
+ )
+
+ _ ->
+ nil
+ end
+ end
+ end
+
+ defp page_search_results(query, %PagingOptions{key: nil}), do: query
+
+ defp page_search_results(query, %PagingOptions{
+ key: {_address_hash, _tx_hash, _block_hash, holder_count, name, inserted_at, item_type}
+ })
+ when holder_count in [nil, ""] do
+ where(
+ query,
+ [item],
+ (item.name > ^name and item.type == ^item_type) or
+ (item.name == ^name and item.inserted_at < ^inserted_at and
+ item.type == ^item_type) or
+ item.type != ^item_type
+ )
+ end
+
+ # credo:disable-for-next-line
+ defp page_search_results(query, %PagingOptions{
+ key: {_address_hash, _tx_hash, _block_hash, holder_count, name, inserted_at, item_type}
+ }) do
+ where(
+ query,
+ [item],
+ (item.holder_count < ^holder_count and item.type == ^item_type) or
+ (item.holder_count == ^holder_count and item.name > ^name and item.type == ^item_type) or
+ (item.holder_count == ^holder_count and item.name == ^name and item.inserted_at < ^inserted_at and
+ item.type == ^item_type) or
+ item.type != ^item_type
+ )
+ end
+
+ defp take_all_categories([], taken_lengths, _remained), do: taken_lengths
+
+ defp take_all_categories(lengths, taken_lengths, remained) do
+ non_zero_count = count_non_zero(lengths)
+
+ target = if(remained < non_zero_count, do: 1, else: div(remained, non_zero_count))
+
+ {lengths_updated, %{result: taken_lengths_reversed}} =
+ Enum.map_reduce(lengths, %{result: [], sum: 0}, fn el, acc ->
+ taken =
+ cond do
+ acc[:sum] >= remained ->
+ 0
+
+ el < target ->
+ el
+
+ true ->
+ target
+ end
+
+ {el - taken, %{result: [taken | acc[:result]], sum: acc[:sum] + taken}}
+ end)
+
+ taken_lengths =
+ taken_lengths
+ |> Enum.zip_reduce(Enum.reverse(taken_lengths_reversed), [], fn x, y, acc -> [x + y | acc] end)
+ |> Enum.reverse()
+
+ remained = remained - Enum.sum(taken_lengths_reversed)
+
+ if remained > 0 and count_non_zero(lengths_updated) > 0 do
+ take_all_categories(lengths_updated, taken_lengths, remained)
+ else
+ taken_lengths
+ end
+ end
+
+ defp count_non_zero(list) do
+ Enum.reduce(list, 0, fn el, acc -> acc + if el > 0, do: 1, else: 0 end)
+ end
+
+ defp compose_result_checksummed_address_hash(result) do
+ if result.address_hash do
+ result
+ |> Map.put(:address_hash, Address.checksum(result.address_hash))
+ else
+ result
+ end
+ end
+
+ # For some reasons timestamp for blocks and txs returns as ~N[2023-06-25 19:39:47.339493]
+ defp format_timestamp(result) do
+ if result.timestamp do
+ result
+ |> Map.put(:timestamp, DateTime.from_naive!(result.timestamp, "Etc/UTC"))
+ else
+ result
+ end
+ end
+
+ defp search_ens_name(search_query, options) do
+ trimmed_query = String.trim(search_query)
+
+ with true <- Regex.match?(~r/\w+\.\w+/, trimmed_query),
+ result when is_map(result) <- ens_domain_name_lookup(search_query) do
+ [
+ result[:address_hash]
+ |> search_address_query()
+ |> select_repo(options).all()
+ |> merge_address_search_result_with_ens_info(result)
+ ]
+ else
+ _ ->
+ []
+ end
+ end
+
+ defp merge_address_search_result_with_ens_info([], ens_info) do
+ %{
+ address_hash: ens_info[:address_hash],
+ block_hash: nil,
+ tx_hash: nil,
+ type: "address",
+ name: nil,
+ symbol: nil,
+ holder_count: nil,
+ inserted_at: nil,
+ block_number: 0,
+ icon_url: nil,
+ token_type: nil,
+ timestamp: nil,
+ verified: false,
+ exchange_rate: nil,
+ total_supply: nil,
+ circulating_market_cap: nil,
+ priority: 0,
+ is_verified_via_admin_panel: nil,
+ ens_info: ens_info
+ }
+ end
+
+ defp merge_address_search_result_with_ens_info([address], ens_info) do
+ Map.put(address |> compose_result_checksummed_address_hash(), :ens_info, ens_info)
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex
index 2da242e39a62..65105cbf853f 100644
--- a/apps/explorer/lib/explorer/chain/smart_contract.ex
+++ b/apps/explorer/lib/explorer/chain/smart_contract.ex
@@ -12,20 +12,49 @@ defmodule Explorer.Chain.SmartContract do
use Explorer.Schema
- alias Ecto.Changeset
- alias EthereumJSONRPC.Contract
+ alias Ecto.{Changeset, Multi}
alias Explorer.Counters.AverageBlockTime
- alias Explorer.{Chain, Repo}
- alias Explorer.Chain.{Address, ContractMethod, DecompiledSmartContract, Hash}
- alias Explorer.Chain.SmartContract.ExternalLibrary
- alias Explorer.SmartContract.Reader
+ alias Explorer.{Chain, Repo, SortingHelper}
+
+ alias Explorer.Chain.{
+ Address,
+ ContractMethod,
+ Data,
+ DecompiledSmartContract,
+ Hash,
+ InternalTransaction,
+ SmartContract,
+ SmartContractAdditionalSource,
+ Transaction
+ }
+
+ alias Explorer.Chain.Address.Name, as: AddressName
+
+ alias Explorer.Chain.SmartContract.{ExternalLibrary, Proxy}
+ alias Explorer.Chain.SmartContract.Proxy.EIP1167
+ alias Explorer.SmartContract.Helper
+ alias Explorer.SmartContract.Solidity.Verifier
alias Timex.Duration
- @burn_address_hash_str "0x0000000000000000000000000000000000000000"
- @burn_address_hash_str_32 "0x0000000000000000000000000000000000000000000000000000000000000000"
-
@typep api? :: {:api?, true | false}
+ @burn_address_hash_string "0x0000000000000000000000000000000000000000"
+ @burn_address_hash_string_32 "0x0000000000000000000000000000000000000000000000000000000000000000"
+
+ defguard is_burn_signature(term) when term in ["0x", "0x0", @burn_address_hash_string_32]
+ defguard is_burn_signature_or_nil(term) when is_burn_signature(term) or term == nil
+ defguard is_burn_signature_extended(term) when is_burn_signature(term) or term == @burn_address_hash_string
+
+ @doc """
+ Returns burn address hash
+ """
+ @spec burn_address_hash_string() :: String.t()
+ def burn_address_hash_string do
+ @burn_address_hash_string
+ end
+
+ @default_sorting [desc: :id]
+
@typedoc """
The name of a parameter to a function or event.
"""
@@ -215,9 +244,10 @@ defmodule Explorer.Chain.SmartContract do
* `implementation_address_hash` - address hash of the proxy's implementation if any
* `autodetect_constructor_args` - field was added for storing user's choice
* `is_yul` - field was added for storing user's choice
+ * `verified_via_eth_bytecode_db` - whether contract automatically verified via eth-bytecode-db or not.
"""
- @type t :: %Explorer.Chain.SmartContract{
+ @type t :: %SmartContract{
name: String.t(),
compiler_version: String.t(),
optimization: boolean,
@@ -238,7 +268,8 @@ defmodule Explorer.Chain.SmartContract do
implementation_fetched_at: DateTime.t(),
implementation_address_hash: Hash.Address.t(),
autodetect_constructor_args: boolean | nil,
- is_yul: boolean | nil
+ is_yul: boolean | nil,
+ verified_via_eth_bytecode_db: boolean | nil
}
schema "smart_contracts" do
@@ -249,7 +280,7 @@ defmodule Explorer.Chain.SmartContract do
field(:constructor_arguments, :string)
field(:evm_version, :string)
field(:optimization_runs, :integer)
- embeds_many(:external_libraries, ExternalLibrary)
+ embeds_many(:external_libraries, ExternalLibrary, on_replace: :delete)
field(:abi, {:array, :map})
field(:verified_via_sourcify, :boolean)
field(:partially_verified, :boolean)
@@ -265,6 +296,7 @@ defmodule Explorer.Chain.SmartContract do
field(:autodetect_constructor_args, :boolean, virtual: true)
field(:is_yul, :boolean, virtual: true)
field(:metadata_from_verified_twin, :boolean, virtual: true)
+ field(:verified_via_eth_bytecode_db, :boolean)
has_many(
:decompiled_smart_contracts,
@@ -309,7 +341,8 @@ defmodule Explorer.Chain.SmartContract do
:implementation_name,
:compiler_settings,
:implementation_address_hash,
- :implementation_fetched_at
+ :implementation_fetched_at,
+ :verified_via_eth_bytecode_db
])
|> validate_required([
:name,
@@ -328,7 +361,7 @@ defmodule Explorer.Chain.SmartContract do
attrs,
error,
error_message,
- json_verification \\ false
+ verification_with_files? \\ false
) do
validated =
smart_contract
@@ -349,14 +382,15 @@ defmodule Explorer.Chain.SmartContract do
:bytecode_checked_at,
:contract_code_md5,
:implementation_name,
- :autodetect_constructor_args
+ :autodetect_constructor_args,
+ :verified_via_eth_bytecode_db
])
- |> (&if(json_verification,
+ |> (&if(verification_with_files?,
do: &1,
else: validate_required(&1, [:compiler_version, :optimization, :address_hash, :contract_code_md5])
)).()
- field_to_put_message = if json_verification, do: :files, else: select_error_field(error)
+ field_to_put_message = if verification_with_files?, do: :files, else: select_error_field(error)
if error_message do
add_error(validated, field_to_put_message, error_message(error, error_message))
@@ -404,53 +438,6 @@ defmodule Explorer.Chain.SmartContract do
end
end
- defp upsert_contract_methods(%Changeset{changes: %{abi: abi}} = changeset) do
- ContractMethod.upsert_from_abi(abi, get_field(changeset, :address_hash))
-
- changeset
- rescue
- exception ->
- message = Exception.format(:error, exception, __STACKTRACE__)
-
- Logger.error(fn -> ["Error while upserting contract methods: ", message] end)
-
- changeset
- end
-
- defp upsert_contract_methods(changeset), do: changeset
-
- defp error_message(:compilation), do: "There was an error compiling your contract."
- defp error_message(:compiler_version), do: "Compiler version does not match, please try again."
- defp error_message(:generated_bytecode), do: "Bytecode does not match, please try again."
- defp error_message(:constructor_arguments), do: "Constructor arguments do not match, please try again."
- defp error_message(:name), do: "Wrong contract name, please try again."
- defp error_message(:json), do: "Invalid JSON file."
-
- defp error_message(:autodetect_constructor_arguments_failed),
- do: "Autodetection of constructor arguments failed. Please try to input constructor arguments manually."
-
- defp error_message(:no_creation_data),
- do: "The contract creation transaction has not been indexed yet. Please wait a few minutes and try again."
-
- defp error_message(:unknown_error), do: "Unable to verify: unknown error."
- defp error_message(:deployed_bytecode), do: "Deployed bytecode does not correspond to contract creation code."
-
- defp error_message(string) when is_binary(string), do: string
-
- defp error_message(_), do: "There was an error validating your contract, please try again."
-
- defp error_message(:compilation, error_message), do: "There was an error compiling your contract: #{error_message}"
-
- defp select_error_field(:no_creation_data), do: :address_hash
- defp select_error_field(:compiler_version), do: :compiler_version
-
- defp select_error_field(constructor_arguments)
- when constructor_arguments in [:constructor_arguments, :autodetect_constructor_arguments_failed],
- do: :constructor_arguments
-
- defp select_error_field(:name), do: :name
- defp select_error_field(_), do: :contract_source_code
-
def merge_twin_contract_with_changeset(%__MODULE__{} = twin_contract, %Changeset{} = changeset) do
%__MODULE__{}
|> changeset(Map.from_struct(twin_contract))
@@ -495,6 +482,10 @@ defmodule Explorer.Chain.SmartContract do
|> Changeset.put_change(:contract_source_code, "")
end
+ @doc """
+ Returns smart-contract changeset with checksummed address hash
+ """
+ @spec address_to_checksum_address(Changeset.t()) :: Changeset.t()
def address_to_checksum_address(changeset) do
checksum_address =
changeset
@@ -505,36 +496,10 @@ defmodule Explorer.Chain.SmartContract do
Changeset.force_change(changeset, :address_hash, checksum_address)
end
- defp to_address_hash(string) when is_binary(string) do
- {:ok, address_hash} = Chain.string_to_address_hash(string)
- address_hash
- end
-
- defp to_address_hash(address_hash), do: address_hash
-
- def proxy_contract?(smart_contract, options \\ [])
-
- def proxy_contract?(%__MODULE__{abi: abi} = smart_contract, options) when not is_nil(abi) do
- implementation_method_abi =
- abi
- |> Enum.find(fn method ->
- Map.get(method, "name") == "implementation" ||
- Chain.master_copy_pattern?(method)
- end)
-
- if implementation_method_abi ||
- not is_nil(
- smart_contract
- |> get_implementation_address_hash(options)
- |> Tuple.to_list()
- |> List.first()
- ),
- do: true,
- else: false
- end
-
- def proxy_contract?(_, _), do: false
-
+ @doc """
+ Returns implementation address and name of the given SmartContract by hash address
+ """
+ @spec get_implementation_address_hash(any(), any()) :: {any(), any()}
def get_implementation_address_hash(smart_contract, options \\ [])
def get_implementation_address_hash(%__MODULE__{abi: nil}, _), do: {nil, nil}
@@ -551,9 +516,9 @@ defmodule Explorer.Chain.SmartContract do
options
) do
updated_smart_contract =
- if Application.get_env(:explorer, :enable_caching_implementation_data_of_proxy) &&
+ if Application.get_env(:explorer, :proxy)[:caching_implementation_data_enabled] &&
check_implementation_refetch_necessity(implementation_fetched_at) do
- Chain.address_hash_to_smart_contract_without_twin(address_hash, options)
+ address_hash_to_smart_contract_without_twin(address_hash, options)
else
smart_contract
end
@@ -575,9 +540,11 @@ defmodule Explorer.Chain.SmartContract do
) do
if check_implementation_refetch_necessity(implementation_fetched_at) do
get_implementation_address_hash_task =
- Task.async(fn -> get_implementation_address_hash(address_hash, abi, metadata_from_verified_twin, options) end)
+ Task.async(fn ->
+ Proxy.fetch_implementation_address_hash(address_hash, abi, metadata_from_verified_twin, options)
+ end)
- timeout = Application.get_env(:explorer, :implementation_data_fetching_timeout)
+ timeout = Application.get_env(:explorer, :proxy)[:implementation_data_fetching_timeout]
case Task.yield(get_implementation_address_hash_task, timeout) ||
Task.ignore(get_implementation_address_hash_task) do
@@ -599,317 +566,734 @@ defmodule Explorer.Chain.SmartContract do
def get_implementation_address_hash(_, _), do: {nil, nil}
- defp db_implementation_data_converter(nil), do: nil
- defp db_implementation_data_converter(string) when is_binary(string), do: string
- defp db_implementation_data_converter(other), do: to_string(other)
+ def save_implementation_data(nil, _, _, _), do: {nil, nil}
- defp check_implementation_refetch_necessity(nil), do: true
+ def save_implementation_data(empty_address_hash_string, proxy_address_hash, metadata_from_verified_twin, options)
+ when is_burn_signature_extended(empty_address_hash_string) do
+ if is_nil(metadata_from_verified_twin) or !metadata_from_verified_twin do
+ proxy_address_hash
+ |> address_hash_to_smart_contract_without_twin(options)
+ |> changeset(%{
+ implementation_name: nil,
+ implementation_address_hash: nil,
+ implementation_fetched_at: DateTime.utc_now()
+ })
+ |> Repo.update()
+ end
- defp check_implementation_refetch_necessity(timestamp) do
- if Application.get_env(:explorer, :enable_caching_implementation_data_of_proxy) do
- now = DateTime.utc_now()
+ {:empty, :empty}
+ end
- average_block_time = get_average_block_time()
+ def save_implementation_data(implementation_address_hash_string, proxy_address_hash, _, options)
+ when is_binary(implementation_address_hash_string) do
+ with {:ok, address_hash} <- Chain.string_to_address_hash(implementation_address_hash_string),
+ proxy_contract <- address_hash_to_smart_contract_without_twin(proxy_address_hash, options),
+ false <- is_nil(proxy_contract),
+ %{implementation: %__MODULE__{name: name}, proxy: proxy_contract} <- %{
+ implementation: address_hash_to_smart_contract(address_hash, options),
+ proxy: proxy_contract
+ } do
+ proxy_contract
+ |> changeset(%{
+ implementation_name: name,
+ implementation_address_hash: implementation_address_hash_string,
+ implementation_fetched_at: DateTime.utc_now()
+ })
+ |> Repo.update()
- fresh_time_distance =
- case average_block_time do
- 0 ->
- Application.get_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy)
+ {implementation_address_hash_string, name}
+ else
+ %{implementation: _, proxy: proxy_contract} ->
+ proxy_contract
+ |> changeset(%{
+ implementation_name: nil,
+ implementation_address_hash: implementation_address_hash_string,
+ implementation_fetched_at: DateTime.utc_now()
+ })
+ |> Repo.update()
- time ->
- round(time)
- end
+ {implementation_address_hash_string, nil}
- timestamp
- |> DateTime.add(fresh_time_distance, :millisecond)
- |> DateTime.compare(now) != :gt
- else
- true
+ true ->
+ {:ok, address_hash} = Chain.string_to_address_hash(implementation_address_hash_string)
+ smart_contract = address_hash_to_smart_contract(address_hash, options)
+
+ {implementation_address_hash_string, smart_contract && smart_contract.name}
+
+ _ ->
+ {implementation_address_hash_string, nil}
end
end
- defp get_average_block_time do
- if Application.get_env(:explorer, :avg_block_time_as_ttl_cached_implementation_data_of_proxy) do
- case AverageBlockTime.average_block_time() do
- {:error, :disabled} ->
- 0
+ @doc """
+ Returns SmartContract by the given smart-contract address hash, if it is partially verified
+ """
+ @spec select_partially_verified_by_address_hash(binary() | Hash.t(), keyword) :: boolean() | nil
+ def select_partially_verified_by_address_hash(address_hash, options \\ []) do
+ query =
+ from(
+ smart_contract in __MODULE__,
+ where: smart_contract.address_hash == ^address_hash,
+ select: smart_contract.partially_verified
+ )
+
+ Chain.select_repo(options).one(query)
+ end
- duration ->
- duration
- |> Duration.to_milliseconds()
+ @doc """
+ Extracts creation bytecode (`init`) and transaction (`tx`) or
+ internal transaction (`internal_tx`) where the contract was created.
+ """
+ @spec creation_tx_with_bytecode(binary() | Hash.t()) ::
+ %{init: binary(), tx: Transaction.t()} | %{init: binary(), internal_tx: InternalTransaction.t()} | nil
+ def creation_tx_with_bytecode(address_hash) do
+ creation_tx_query =
+ from(
+ tx in Transaction,
+ where: tx.created_contract_address_hash == ^address_hash,
+ where: tx.status == ^1
+ )
+
+ tx =
+ creation_tx_query
+ |> Repo.one()
+
+ if tx do
+ with %{input: input} <- tx do
+ %{init: Data.to_string(input), tx: tx}
end
else
- 0
+ creation_int_tx_query =
+ from(
+ itx in InternalTransaction,
+ join: t in assoc(itx, :transaction),
+ where: itx.created_contract_address_hash == ^address_hash,
+ where: t.status == ^1
+ )
+
+ internal_tx = creation_int_tx_query |> Repo.one()
+
+ case internal_tx do
+ %{init: init} ->
+ init_str = Data.to_string(init)
+ %{init: init_str, internal_tx: internal_tx}
+
+ _ ->
+ nil
+ end
end
end
- @spec get_implementation_address_hash(Hash.Address.t(), list(), boolean() | nil, [api?]) ::
- {String.t() | nil, String.t() | nil}
- defp get_implementation_address_hash(proxy_address_hash, abi, metadata_from_verified_twin, options)
- when not is_nil(proxy_address_hash) and not is_nil(abi) do
- implementation_method_abi =
- abi
- |> Enum.find(fn method ->
- Map.get(method, "name") == "implementation" && Map.get(method, "stateMutability") == "view"
- end)
+ @doc """
+ Composes address object for smart-contract
+ """
+ @spec compose_smart_contract(map(), Hash.t(), any()) :: map()
+ def compose_smart_contract(address_result, hash, options) do
+ address_verified_twin_contract =
+ EIP1167.get_implementation_address(hash, options) ||
+ get_address_verified_twin_contract(hash, options).verified_contract
+
+ if address_verified_twin_contract do
+ address_verified_twin_contract_updated =
+ address_verified_twin_contract
+ |> Map.put(:address_hash, hash)
+ |> Map.put(:metadata_from_verified_twin, true)
+ |> Map.put(:implementation_address_hash, nil)
+ |> Map.put(:implementation_name, nil)
+ |> Map.put(:implementation_fetched_at, nil)
+
+ address_result
+ |> Map.put(:smart_contract, address_verified_twin_contract_updated)
+ else
+ address_result
+ end
+ end
+
+ @doc """
+ Finds metadata for verification of a contract from verified twins: contracts with the same bytecode
+ which were verified previously, returns a single t:SmartContract.t/0
+ """
+ @spec get_address_verified_twin_contract(Hash.t() | String.t(), any()) :: %{
+ :verified_contract => any(),
+ :additional_sources => SmartContractAdditionalSource.t() | nil
+ }
+ def get_address_verified_twin_contract(hash, options \\ [])
+
+ def get_address_verified_twin_contract(hash, options) when is_binary(hash) do
+ case Chain.string_to_address_hash(hash) do
+ {:ok, address_hash} -> get_address_verified_twin_contract(address_hash, options)
+ _ -> %{:verified_contract => nil, :additional_sources => nil}
+ end
+ end
+
+ def get_address_verified_twin_contract(%Hash{} = address_hash, options) do
+ with target_address <- Chain.select_repo(options).get(Address, address_hash),
+ false <- is_nil(target_address) do
+ verified_contract_twin = get_verified_twin_contract(target_address, options)
+
+ verified_contract_twin_additional_sources =
+ SmartContractAdditionalSource.get_contract_additional_sources(verified_contract_twin, options)
+
+ %{
+ :verified_contract => check_and_update_constructor_args(verified_contract_twin),
+ :additional_sources => verified_contract_twin_additional_sources
+ }
+ else
+ _ ->
+ %{:verified_contract => nil, :additional_sources => nil}
+ end
+ end
+
+ @doc """
+ Returns verified smart-contract with the same bytecode of the given smart-contract
+ """
+ @spec get_verified_twin_contract(Address.t(), any()) :: SmartContract.t() | nil
+ def get_verified_twin_contract(%Address{} = target_address, options \\ []) do
+ case target_address do
+ %{contract_code: %Chain.Data{bytes: contract_code_bytes}} ->
+ target_address_hash = target_address.hash
+
+ contract_code_md5 = Helper.contract_code_md5(contract_code_bytes)
+
+ verified_contract_twin_query =
+ from(
+ smart_contract in __MODULE__,
+ where: smart_contract.contract_code_md5 == ^contract_code_md5,
+ where: smart_contract.address_hash != ^target_address_hash,
+ select: smart_contract,
+ limit: 1
+ )
+
+ verified_contract_twin_query
+ |> Chain.select_repo(options).one(timeout: 10_000)
+
+ _ ->
+ nil
+ end
+ end
+
+ @doc """
+ Returns address or smart_contract object with parsed constructor_arguments
+ """
+ @spec check_and_update_constructor_args(any()) :: any()
+ def check_and_update_constructor_args(
+ %__MODULE__{address_hash: address_hash, constructor_arguments: nil, verified_via_sourcify: true} =
+ smart_contract
+ ) do
+ if args = Verifier.parse_constructor_arguments_for_sourcify_contract(address_hash, smart_contract.abi) do
+ smart_contract |> __MODULE__.changeset(%{constructor_arguments: args}) |> Repo.update()
+ %__MODULE__{smart_contract | constructor_arguments: args}
+ else
+ smart_contract
+ end
+ end
+
+ def check_and_update_constructor_args(
+ %Address{
+ hash: address_hash,
+ contract_code: deployed_bytecode,
+ smart_contract: %__MODULE__{constructor_arguments: nil, verified_via_sourcify: true} = smart_contract
+ } = address
+ ) do
+ if args =
+ Verifier.parse_constructor_arguments_for_sourcify_contract(address_hash, smart_contract.abi, deployed_bytecode) do
+ smart_contract |> __MODULE__.changeset(%{constructor_arguments: args}) |> Repo.update()
+ %Address{address | smart_contract: %__MODULE__{smart_contract | constructor_arguments: args}}
+ else
+ address
+ end
+ end
- get_implementation_method_abi =
- abi
- |> Enum.find(fn method ->
- Map.get(method, "name") == "getImplementation" && Map.get(method, "stateMutability") == "view"
+ def check_and_update_constructor_args(other), do: other
+
+ @doc """
+ Adds verified metadata from bytecode twin smart-contract to the given smart-contract
+ """
+ @spec add_twin_info_to_contract(map(), Chain.Hash.t(), Chain.Hash.t() | nil) :: map()
+ def add_twin_info_to_contract(address_result, address_verified_twin_contract, _hash)
+ when is_nil(address_verified_twin_contract),
+ do: address_result
+
+ def add_twin_info_to_contract(address_result, address_verified_twin_contract, hash) do
+ address_verified_twin_contract_updated =
+ address_verified_twin_contract
+ |> Map.put(:address_hash, hash)
+ |> Map.put(:metadata_from_verified_twin, true)
+ |> Map.put(:implementation_address_hash, nil)
+ |> Map.put(:implementation_name, nil)
+ |> Map.put(:implementation_fetched_at, nil)
+
+ address_result
+ |> Map.put(:smart_contract, address_verified_twin_contract_updated)
+ end
+
+ @doc """
+ Inserts a `t:SmartContract.t/0`.
+
+ As part of inserting a new smart contract, an additional record is inserted for
+ naming the address for reference.
+ """
+ @spec create_smart_contract(map(), list(), list()) :: {:ok, __MODULE__.t()} | {:error, Ecto.Changeset.t()}
+ def create_smart_contract(attrs \\ %{}, external_libraries \\ [], secondary_sources \\ []) do
+ new_contract = %__MODULE__{}
+
+ attrs =
+ attrs
+ |> Helper.add_contract_code_md5()
+
+ smart_contract_changeset =
+ new_contract
+ |> __MODULE__.changeset(attrs)
+ |> Changeset.put_change(:external_libraries, external_libraries)
+
+ new_contract_additional_source = %SmartContractAdditionalSource{}
+
+ smart_contract_additional_sources_changesets =
+ if secondary_sources do
+ secondary_sources
+ |> Enum.map(fn changeset ->
+ new_contract_additional_source
+ |> SmartContractAdditionalSource.changeset(changeset)
+ end)
+ else
+ []
+ end
+
+ address_hash = Changeset.get_field(smart_contract_changeset, :address_hash)
+
+ # Enforce ShareLocks tables order (see docs: sharelocks.md)
+ insert_contract_query =
+ Multi.new()
+ |> Multi.run(:set_address_verified, fn repo, _ -> set_address_verified(repo, address_hash) end)
+ |> Multi.run(:clear_primary_address_names, fn repo, _ ->
+ AddressName.clear_primary_address_names(repo, address_hash)
end)
+ |> Multi.insert(:smart_contract, smart_contract_changeset)
- master_copy_method_abi =
- abi
- |> Enum.find(fn method ->
- Chain.master_copy_pattern?(method)
+ insert_contract_query_with_additional_sources =
+ smart_contract_additional_sources_changesets
+ |> Enum.with_index()
+ |> Enum.reduce(insert_contract_query, fn {changeset, index}, multi ->
+ Multi.insert(multi, "smart_contract_additional_source_#{Integer.to_string(index)}", changeset)
end)
- implementation_address =
- cond do
- implementation_method_abi ->
- get_implementation_address_hash_basic("5c60da1b", proxy_address_hash, abi)
+ insert_result =
+ insert_contract_query_with_additional_sources
+ |> Repo.transaction()
+
+ AddressName.create_primary_address_name(Repo, Changeset.get_field(smart_contract_changeset, :name), address_hash)
+
+ case insert_result do
+ {:ok, %{smart_contract: smart_contract}} ->
+ {:ok, smart_contract}
+
+ {:error, :smart_contract, changeset, _} ->
+ {:error, changeset}
+
+ {:error, :set_address_verified, message, _} ->
+ {:error, message}
+ end
+ end
+
+ @doc """
+ Updates a `t:SmartContract.t/0`.
+
+ Has the similar logic as create_smart_contract/1.
+ Used in cases when you need to update row in DB contains SmartContract, e.g. in case of changing
+ status `partially verified` to `fully verified` (re-verify).
+ """
+ @spec update_smart_contract(map(), list(), list()) :: {:ok, __MODULE__.t()} | {:error, Ecto.Changeset.t()}
+ def update_smart_contract(attrs \\ %{}, external_libraries \\ [], secondary_sources \\ []) do
+ address_hash = Map.get(attrs, :address_hash)
+
+ query_sources =
+ from(
+ source in SmartContractAdditionalSource,
+ where: source.address_hash == ^address_hash
+ )
- get_implementation_method_abi ->
- get_implementation_address_hash_basic("aaf10f42", proxy_address_hash, abi)
+ _delete_sources = Repo.delete_all(query_sources)
- master_copy_method_abi ->
- get_implementation_address_hash_from_master_copy_pattern(proxy_address_hash)
+ query = get_smart_contract_query(address_hash)
+ smart_contract = Repo.one(query)
- true ->
- get_implementation_address_hash_eip_1967(proxy_address_hash)
+ smart_contract_changeset =
+ smart_contract
+ |> __MODULE__.changeset(attrs)
+ |> Changeset.put_change(:external_libraries, external_libraries)
+
+ new_contract_additional_source = %SmartContractAdditionalSource{}
+
+ smart_contract_additional_sources_changesets =
+ if secondary_sources do
+ secondary_sources
+ |> Enum.map(fn changeset ->
+ new_contract_additional_source
+ |> SmartContractAdditionalSource.changeset(changeset)
+ end)
+ else
+ []
end
- save_implementation_data(implementation_address, proxy_address_hash, metadata_from_verified_twin, options)
+ # Enforce ShareLocks tables order (see docs: sharelocks.md)
+ insert_contract_query =
+ Multi.new()
+ |> Multi.run(:clear_primary_address_names, fn repo, _ ->
+ AddressName.clear_primary_address_names(repo, address_hash)
+ end)
+ |> Multi.update(:smart_contract, smart_contract_changeset)
+
+ insert_contract_query_with_additional_sources =
+ smart_contract_additional_sources_changesets
+ |> Enum.with_index()
+ |> Enum.reduce(insert_contract_query, fn {changeset, index}, multi ->
+ Multi.insert(multi, "smart_contract_additional_source_#{Integer.to_string(index)}", changeset)
+ end)
+
+ insert_result =
+ insert_contract_query_with_additional_sources
+ |> Repo.transaction()
+
+ AddressName.create_primary_address_name(Repo, Changeset.get_field(smart_contract_changeset, :name), address_hash)
+
+ case insert_result do
+ {:ok, %{smart_contract: smart_contract}} ->
+ {:ok, smart_contract}
+
+ {:error, :smart_contract, changeset, _} ->
+ {:error, changeset}
+
+ {:error, :set_address_verified, message, _} ->
+ {:error, message}
+ end
end
- defp get_implementation_address_hash(_proxy_address_hash, _abi, _, _) do
- {nil, nil}
+ @doc """
+ Converts address hash to smart-contract object
+ """
+ @spec address_hash_to_smart_contract_without_twin(Hash.Address.t(), [api?]) :: __MODULE__.t() | nil
+ def address_hash_to_smart_contract_without_twin(address_hash, options) do
+ query = get_smart_contract_query(address_hash)
+
+ Chain.select_repo(options).one(query)
end
- defp get_implementation_address_hash_eip_1967(proxy_address_hash) do
- json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
+ @doc """
+ Converts address hash to smart-contract object with metadata_from_verified_twin=true
+ """
+ @spec address_hash_to_smart_contract(Hash.Address.t(), [api?]) :: __MODULE__.t() | nil
+ def address_hash_to_smart_contract(address_hash, options \\ []) do
+ current_smart_contract = address_hash_to_smart_contract_without_twin(address_hash, options)
+
+ with true <- is_nil(current_smart_contract),
+ address_verified_twin_contract =
+ EIP1167.get_implementation_address(address_hash, options) ||
+ get_address_verified_twin_contract(address_hash, options).verified_contract,
+ false <- is_nil(address_verified_twin_contract) do
+ address_verified_twin_contract
+ |> Map.put(:address_hash, address_hash)
+ |> Map.put(:metadata_from_verified_twin, true)
+ |> Map.put(:implementation_address_hash, nil)
+ |> Map.put(:implementation_name, nil)
+ |> Map.put(:implementation_fetched_at, nil)
+ else
+ _ ->
+ current_smart_contract
+ end
+ end
- # https://eips.ethereum.org/EIPS/eip-1967
- storage_slot_logic_contract_address = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
+ @doc """
+ Checks if it exists a verified `t:Explorer.Chain.SmartContract.t/0` for the
+ `t:Explorer.Chain.Address.t/0` with the provided `hash` and `partially_verified` property is not true.
- {_status, implementation_address} =
- case Contract.eth_get_storage_at_request(
- proxy_address_hash,
- storage_slot_logic_contract_address,
- nil,
- json_rpc_named_arguments
- ) do
- {:ok, empty_address}
- when empty_address in ["0x", "0x0", @burn_address_hash_str_32, nil] ->
- fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments)
+ Returns `true` if found and `false` otherwise.
+ """
+ @spec verified_with_full_match?(Hash.Address.t() | String.t()) :: boolean()
+ def verified_with_full_match?(address_hash, options \\ [])
- {:ok, implementation_logic_address} ->
- {:ok, implementation_logic_address}
+ def verified_with_full_match?(address_hash_str, options) when is_binary(address_hash_str) do
+ case Chain.string_to_address_hash(address_hash_str) do
+ {:ok, address_hash} ->
+ check_verified_with_full_match(address_hash, options)
- _ ->
- {:ok, nil}
- end
+ _ ->
+ false
+ end
+ end
- abi_decode_address_output(implementation_address)
+ def verified_with_full_match?(address_hash, options) do
+ check_verified_with_full_match(address_hash, options)
end
- # changes requested by https://github.com/blockscout/blockscout/issues/4770
- # for support BeaconProxy pattern
- defp fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) do
- # https://eips.ethereum.org/EIPS/eip-1967
- storage_slot_beacon_contract_address = "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50"
+ @doc """
+ Checks if it exists a verified `t:Explorer.Chain.SmartContract.t/0` for the
+ `t:Explorer.Chain.Address.t/0` with the provided `hash`.
- implementation_method_abi = [
- %{
- "type" => "function",
- "stateMutability" => "view",
- "outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}],
- "name" => "implementation",
- "inputs" => []
- }
- ]
-
- case Contract.eth_get_storage_at_request(
- proxy_address_hash,
- storage_slot_beacon_contract_address,
- nil,
- json_rpc_named_arguments
- ) do
- {:ok, empty_address}
- when empty_address in ["0x", "0x0", @burn_address_hash_str_32, nil] ->
- fetch_openzeppelin_proxy_implementation(proxy_address_hash, json_rpc_named_arguments)
-
- {:ok, beacon_contract_address} ->
- case beacon_contract_address
- |> abi_decode_address_output()
- |> get_implementation_address_hash_basic("5c60da1b", implementation_method_abi) do
- <> ->
- {:ok, implementation_address}
-
- _ ->
- {:ok, beacon_contract_address}
- end
+ Returns `true` if found and `false` otherwise.
+ """
+ @spec verified?(Hash.Address.t() | String.t()) :: boolean()
+ def verified?(address_hash_str) when is_binary(address_hash_str) do
+ case Chain.string_to_address_hash(address_hash_str) do
+ {:ok, address_hash} ->
+ verified_smart_contract_exists?(address_hash)
_ ->
- {:ok, nil}
+ false
end
end
- # changes requested by https://github.com/blockscout/blockscout/issues/5292
- defp fetch_openzeppelin_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) do
- # This is the keccak-256 hash of "org.zeppelinos.proxy.implementation"
- storage_slot_logic_contract_address = "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3"
+ def verified?(address_hash) do
+ verified_smart_contract_exists?(address_hash)
+ end
- case Contract.eth_get_storage_at_request(
- proxy_address_hash,
- storage_slot_logic_contract_address,
- nil,
- json_rpc_named_arguments
- ) do
- {:ok, empty_address}
- when empty_address in ["0x", "0x0", @burn_address_hash_str_32] ->
- {:ok, "0x"}
+ @doc """
+ Checks if it exists a verified `t:Explorer.Chain.SmartContract.t/0` for the
+ `t:Explorer.Chain.Address.t/0` with the provided `hash`.
- {:ok, logic_contract_address} ->
- {:ok, logic_contract_address}
+ Returns `:ok` if found and `:not_found` otherwise.
+ """
+ @spec check_verified_smart_contract_exists(Hash.Address.t()) :: :ok | :not_found
+ def check_verified_smart_contract_exists(address_hash) do
+ address_hash
+ |> verified_smart_contract_exists?()
+ |> Chain.boolean_to_check_result()
+ end
+ @doc """
+ Gets smart-contract ABI from the DB for the given address hash of smart-contract
+ """
+ @spec get_smart_contract_abi(String.t(), any()) :: any()
+ def get_smart_contract_abi(address_hash_string, options \\ [])
+
+ def get_smart_contract_abi(address_hash_string, options)
+ when not is_nil(address_hash_string) do
+ with {:ok, implementation_address_hash} <- Chain.string_to_address_hash(address_hash_string),
+ implementation_smart_contract =
+ implementation_address_hash
+ |> address_hash_to_smart_contract(options),
+ false <- is_nil(implementation_smart_contract) do
+ implementation_smart_contract
+ |> Map.get(:abi)
+ else
_ ->
- {:ok, nil}
+ []
end
end
- defp get_implementation_address_hash_basic(signature, proxy_address_hash, abi) do
- # supported signatures:
- # 5c60da1b = keccak256(implementation())
- # aaf10f42 = keccak256(getImplementation())
- implementation_address =
- case Reader.query_contract(
- proxy_address_hash,
- abi,
- %{
- "#{signature}" => []
- },
- false
- ) do
- %{^signature => {:ok, [result]}} -> result
- _ -> nil
- end
+ def get_smart_contract_abi(address_hash_string, _) when is_nil(address_hash_string) do
+ []
+ end
+
+ @doc """
+ Gets smart-contract by address hash
+ """
+ @spec get_smart_contract_query(Hash.Address.t() | binary) :: Ecto.Query.t()
+ def get_smart_contract_query(address_hash) do
+ from(
+ smart_contract in __MODULE__,
+ where: smart_contract.address_hash == ^address_hash
+ )
+ end
+
+ defp upsert_contract_methods(%Changeset{changes: %{abi: abi}} = changeset) do
+ ContractMethod.upsert_from_abi(abi, get_field(changeset, :address_hash))
- address_to_hex(implementation_address)
+ changeset
+ rescue
+ exception ->
+ message = Exception.format(:error, exception, __STACKTRACE__)
+
+ Logger.error(fn -> ["Error while upserting contract methods: ", message] end)
+
+ changeset
end
- defp get_implementation_address_hash_from_master_copy_pattern(proxy_address_hash) do
- json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
+ defp upsert_contract_methods(changeset), do: changeset
- master_copy_storage_pointer = "0x0"
+ defp error_message(:compilation), do: error_message_with_log("There was an error compiling your contract.")
- {:ok, implementation_address} =
- case Contract.eth_get_storage_at_request(
- proxy_address_hash,
- master_copy_storage_pointer,
- nil,
- json_rpc_named_arguments
- ) do
- {:ok, empty_address}
- when empty_address in ["0x", "0x0", @burn_address_hash_str_32] ->
- {:ok, "0x"}
+ defp error_message(:compiler_version),
+ do: error_message_with_log("Compiler version does not match, please try again.")
- {:ok, logic_contract_address} ->
- {:ok, logic_contract_address}
+ defp error_message(:generated_bytecode), do: error_message_with_log("Bytecode does not match, please try again.")
- _ ->
- {:ok, nil}
- end
+ defp error_message(:constructor_arguments),
+ do: error_message_with_log("Constructor arguments do not match, please try again.")
- abi_decode_address_output(implementation_address)
+ defp error_message(:name), do: error_message_with_log("Wrong contract name, please try again.")
+ defp error_message(:json), do: error_message_with_log("Invalid JSON file.")
+
+ defp error_message(:autodetect_constructor_arguments_failed),
+ do:
+ error_message_with_log(
+ "Autodetection of constructor arguments failed. Please try to input constructor arguments manually."
+ )
+
+ defp error_message(:no_creation_data),
+ do:
+ error_message_with_log(
+ "The contract creation transaction has not been indexed yet. Please wait a few minutes and try again."
+ )
+
+ defp error_message(:unknown_error), do: error_message_with_log("Unable to verify: unknown error.")
+
+ defp error_message(:deployed_bytecode),
+ do: error_message_with_log("Deployed bytecode does not correspond to contract creation code.")
+
+ defp error_message(:contract_source_code), do: error_message_with_log("Empty contract source code.")
+
+ defp error_message(string) when is_binary(string), do: error_message_with_log(string)
+ defp error_message(%{"message" => string} = error) when is_map(error), do: error_message_with_log(string)
+
+ defp error_message(error) do
+ Logger.warn(fn -> ["Unknown verifier error: ", inspect(error)] end)
+ "There was an error validating your contract, please try again."
end
- defp save_implementation_data(nil, _, _, _), do: {nil, nil}
+ defp error_message(:compilation, error_message),
+ do: error_message_with_log("There was an error compiling your contract: #{error_message}")
- defp save_implementation_data(empty_address_hash_string, proxy_address_hash, metadata_from_verified_twin, options)
- when empty_address_hash_string in [
- "0x",
- "0x0",
- @burn_address_hash_str_32,
- @burn_address_hash_str
- ] do
- if is_nil(metadata_from_verified_twin) or !metadata_from_verified_twin do
- proxy_address_hash
- |> Chain.address_hash_to_smart_contract_without_twin(options)
- |> changeset(%{
- implementation_name: nil,
- implementation_address_hash: nil,
- implementation_fetched_at: DateTime.utc_now()
- })
- |> Repo.update()
- end
+ defp error_message_with_log(error_string) do
+ Logger.error("Smart-contract verification error: #{error_string}")
+ error_string
+ end
- {:empty, :empty}
+ defp select_error_field(:no_creation_data), do: :address_hash
+ defp select_error_field(:compiler_version), do: :compiler_version
+
+ defp select_error_field(constructor_arguments)
+ when constructor_arguments in [:constructor_arguments, :autodetect_constructor_arguments_failed],
+ do: :constructor_arguments
+
+ defp select_error_field(:name), do: :name
+ defp select_error_field(_), do: :contract_source_code
+
+ defp to_address_hash(string) when is_binary(string) do
+ {:ok, address_hash} = Chain.string_to_address_hash(string)
+ address_hash
end
- defp save_implementation_data(implementation_address_hash_string, proxy_address_hash, _, options)
- when is_binary(implementation_address_hash_string) do
- with {:ok, address_hash} <- Chain.string_to_address_hash(implementation_address_hash_string),
- proxy_contract <- Chain.address_hash_to_smart_contract_without_twin(proxy_address_hash, options),
- false <- is_nil(proxy_contract),
- %{implementation: %__MODULE__{name: name}, proxy: proxy_contract} <- %{
- implementation: Chain.address_hash_to_smart_contract(address_hash, options),
- proxy: proxy_contract
- } do
- proxy_contract
- |> changeset(%{
- implementation_name: name,
- implementation_address_hash: implementation_address_hash_string,
- implementation_fetched_at: DateTime.utc_now()
- })
- |> Repo.update()
+ defp to_address_hash(address_hash), do: address_hash
- {implementation_address_hash_string, name}
- else
- %{implementation: _, proxy: proxy_contract} ->
- proxy_contract
- |> changeset(%{
- implementation_name: nil,
- implementation_address_hash: implementation_address_hash_string,
- implementation_fetched_at: DateTime.utc_now()
- })
- |> Repo.update()
+ defp db_implementation_data_converter(nil), do: nil
+ defp db_implementation_data_converter(string) when is_binary(string), do: string
+ defp db_implementation_data_converter(other), do: to_string(other)
- {implementation_address_hash_string, nil}
+ defp check_implementation_refetch_necessity(nil), do: true
- true ->
- {:ok, address_hash} = Chain.string_to_address_hash(implementation_address_hash_string)
- smart_contract = Chain.address_hash_to_smart_contract(address_hash, options)
+ defp check_implementation_refetch_necessity(timestamp) do
+ if Application.get_env(:explorer, :proxy)[:caching_implementation_data_enabled] do
+ now = DateTime.utc_now()
- {implementation_address_hash_string, smart_contract && smart_contract.name}
+ average_block_time = get_average_block_time_for_implementation_refetch()
- _ ->
- {implementation_address_hash_string, nil}
+ fresh_time_distance =
+ case average_block_time do
+ 0 ->
+ Application.get_env(:explorer, :proxy)[:fallback_cached_implementation_data_ttl]
+
+ time ->
+ round(time)
+ end
+
+ timestamp
+ |> DateTime.add(fresh_time_distance, :millisecond)
+ |> DateTime.compare(now) != :gt
+ else
+ true
end
end
- defp address_to_hex(address) do
- if address do
- if String.starts_with?(address, "0x") do
- address
- else
- "0x" <> Base.encode16(address, case: :lower)
+ defp get_average_block_time_for_implementation_refetch do
+ if Application.get_env(:explorer, :proxy)[:implementation_data_ttl_via_avg_block_time] do
+ case AverageBlockTime.average_block_time() do
+ {:error, :disabled} ->
+ 0
+
+ duration ->
+ duration
+ |> Duration.to_milliseconds()
end
+ else
+ 0
end
end
- defp abi_decode_address_output(nil), do: nil
+ @spec verified_smart_contract_exists?(Hash.Address.t()) :: boolean()
+ defp verified_smart_contract_exists?(address_hash) do
+ query = get_smart_contract_query(address_hash)
- defp abi_decode_address_output("0x"), do: @burn_address_hash_str
+ Repo.exists?(query)
+ end
- defp abi_decode_address_output(address) when is_binary(address) do
- if String.length(address) > 42 do
- "0x" <> String.slice(address, -40, 40)
- else
- address
+ defp set_address_verified(repo, address_hash) do
+ query =
+ from(
+ address in Address,
+ where: address.hash == ^address_hash
+ )
+
+ case repo.update_all(query, set: [verified: true]) do
+ {1, _} -> {:ok, []}
+ _ -> {:error, "There was an error annotating that the address has been verified."}
end
end
- defp abi_decode_address_output(_), do: nil
+ defp check_verified_with_full_match(address_hash, options) do
+ smart_contract = address_hash_to_smart_contract_without_twin(address_hash, options)
+
+ if smart_contract, do: !smart_contract.partially_verified, else: false
+ end
+
+ @spec verified_contracts([
+ Chain.paging_options()
+ | Chain.necessity_by_association_option()
+ | {:filter, :solidity | :vyper | :yul}
+ | {:search, String.t()}
+ | {:sorting, SortingHelper.sorting_params()}
+ | Chain.api?()
+ ]) :: [__MODULE__.t()]
+ def verified_contracts(options \\ []) do
+ paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options())
+ sorting_options = Keyword.get(options, :sorting, [])
+ necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
+ filter = Keyword.get(options, :filter, nil)
+ search_string = Keyword.get(options, :search, nil)
+
+ query = from(contract in __MODULE__)
+
+ query
+ |> filter_contracts(filter)
+ |> search_contracts(search_string)
+ |> SortingHelper.apply_sorting(sorting_options, @default_sorting)
+ |> SortingHelper.page_with_sorting(paging_options, sorting_options, @default_sorting)
+ |> Chain.join_associations(necessity_by_association)
+ |> Chain.select_repo(options).all()
+ end
+
+ defp search_contracts(basic_query, nil), do: basic_query
+
+ defp search_contracts(basic_query, search_string) do
+ from(contract in basic_query,
+ where:
+ ilike(contract.name, ^"%#{search_string}%") or
+ ilike(fragment("'0x' || encode(?, 'hex')", contract.address_hash), ^"%#{search_string}%")
+ )
+ end
+
+ defp filter_contracts(basic_query, :solidity) do
+ basic_query
+ |> where(is_vyper_contract: ^false)
+ end
+
+ defp filter_contracts(basic_query, :vyper) do
+ basic_query
+ |> where(is_vyper_contract: ^true)
+ end
+
+ defp filter_contracts(basic_query, :yul) do
+ from(query in basic_query, where: is_nil(query.abi))
+ end
+
+ defp filter_contracts(basic_query, _), do: basic_query
end
diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex
new file mode 100644
index 000000000000..cbd6dd4ee55f
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex
@@ -0,0 +1,292 @@
+defmodule Explorer.Chain.SmartContract.Proxy do
+ @moduledoc """
+ Module for proxy smart-contract implementation detection
+ """
+
+ alias EthereumJSONRPC.Contract
+ alias Explorer.Chain.{Hash, SmartContract}
+ alias Explorer.Chain.SmartContract.Proxy
+ alias Explorer.Chain.SmartContract.Proxy.{Basic, EIP1167, EIP1822, EIP1967, EIP930, MasterCopy}
+
+ import Explorer.Chain,
+ only: [
+ string_to_address_hash: 1
+ ]
+
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0, is_burn_signature_or_nil: 1]
+
+ # supported signatures:
+ # 5c60da1b = keccak256(implementation())
+ @implementation_signature "5c60da1b"
+ # aaf10f42 = keccak256(getImplementation())
+ @get_implementation_signature "aaf10f42"
+ # bb82aa5e = keccak256(comptrollerImplementation()) Compound protocol proxy pattern
+ @comptroller_implementation_signature "bb82aa5e"
+ # aaf10f42 = keccak256(getAddress(bytes32))
+ @get_address_signature "21f8a721"
+
+ @typep api? :: {:api?, true | false}
+
+ @doc """
+ Fetches into DB proxy contract implementation's address and name from different proxy patterns
+ """
+ @spec fetch_implementation_address_hash(Hash.Address.t(), list(), boolean() | nil, [api?]) ::
+ {String.t() | nil, String.t() | nil}
+ def fetch_implementation_address_hash(proxy_address_hash, proxy_abi, metadata_from_verified_twin, options)
+ when not is_nil(proxy_address_hash) and not is_nil(proxy_abi) do
+ implementation_address_hash_string = get_implementation_address_hash_string(proxy_address_hash, proxy_abi)
+
+ SmartContract.save_implementation_data(
+ implementation_address_hash_string,
+ proxy_address_hash,
+ metadata_from_verified_twin,
+ options
+ )
+ end
+
+ def fetch_implementation_address_hash(_, _, _, _) do
+ {nil, nil}
+ end
+
+ @doc """
+ Checks if smart-contract is proxy. Returns true/false.
+ """
+ @spec proxy_contract?(SmartContract.t(), any()) :: boolean()
+ def proxy_contract?(smart_contract, options \\ []) do
+ {:ok, burn_address_hash} = string_to_address_hash(SmartContract.burn_address_hash_string())
+
+ if smart_contract.implementation_address_hash &&
+ smart_contract.implementation_address_hash.bytes !== burn_address_hash.bytes do
+ true
+ else
+ {implementation_address_hash_string, _} = SmartContract.get_implementation_address_hash(smart_contract, options)
+
+ with false <- is_nil(implementation_address_hash_string),
+ {:ok, implementation_address_hash} <- string_to_address_hash(implementation_address_hash_string),
+ false <- implementation_address_hash.bytes == burn_address_hash.bytes do
+ true
+ else
+ _ ->
+ false
+ end
+ end
+ end
+
+ @doc """
+ Decodes address output into 20 bytes address hash
+ """
+ @spec abi_decode_address_output(any()) :: nil | binary()
+ def abi_decode_address_output(nil), do: nil
+
+ def abi_decode_address_output("0x"), do: SmartContract.burn_address_hash_string()
+
+ def abi_decode_address_output(address) when is_binary(address) do
+ if String.length(address) > 42 do
+ "0x" <> String.slice(address, -40, 40)
+ else
+ address
+ end
+ end
+
+ def abi_decode_address_output(_), do: nil
+
+ @doc """
+ Gets implementation ABI for given proxy smart-contract
+ """
+ @spec get_implementation_abi_from_proxy(any(), any()) :: [map()]
+ def get_implementation_abi_from_proxy(
+ %SmartContract{address_hash: proxy_address_hash, abi: abi} = smart_contract,
+ options
+ )
+ when not is_nil(proxy_address_hash) and not is_nil(abi) do
+ {implementation_address_hash_string, _name} = SmartContract.get_implementation_address_hash(smart_contract, options)
+ SmartContract.get_smart_contract_abi(implementation_address_hash_string)
+ end
+
+ def get_implementation_abi_from_proxy(_, _), do: []
+
+ @doc """
+ Checks if the ABI of the smart-contract follows GnosisSafe proxy pattern
+ """
+ @spec gnosis_safe_contract?([map()]) :: boolean()
+ def gnosis_safe_contract?(abi) when not is_nil(abi) do
+ if get_master_copy_pattern(abi), do: true, else: false
+ end
+
+ def gnosis_safe_contract?(abi) when is_nil(abi), do: false
+
+ @doc """
+ Checks if the input of the smart-contract follows master-copy (or Safe) proxy pattern before
+ fetching its implementation from 0x0 storage pointer
+ """
+ @spec master_copy_pattern?(map()) :: any()
+ def master_copy_pattern?(method) do
+ Map.get(method, "type") == "constructor" &&
+ method
+ |> Enum.find(fn item ->
+ case item do
+ {"inputs", inputs} ->
+ find_input_by_name(inputs, "_masterCopy") || find_input_by_name(inputs, "_singleton")
+
+ _ ->
+ false
+ end
+ end)
+ end
+
+ @doc """
+ Gets implementation from proxy contract's specific storage
+ """
+ @spec get_implementation_from_storage(Hash.Address.t(), String.t(), any()) :: String.t() | nil
+ def get_implementation_from_storage(proxy_address_hash, storage_slot, json_rpc_named_arguments) do
+ case Contract.eth_get_storage_at_request(
+ proxy_address_hash,
+ storage_slot,
+ nil,
+ json_rpc_named_arguments
+ ) do
+ {:ok, empty_address_hash_string}
+ when is_burn_signature_or_nil(empty_address_hash_string) ->
+ nil
+
+ {:ok, implementation_logic_address_hash_string} ->
+ implementation_logic_address_hash_string
+
+ _ ->
+ nil
+ end
+ end
+
+ defp get_implementation_address_hash_string(proxy_address_hash, proxy_abi) do
+ get_implementation_address_hash_string_eip1167(
+ proxy_address_hash,
+ proxy_abi
+ )
+ end
+
+ @doc """
+ Returns EIP-1167 implementation address or tries next proxy pattern
+ """
+ @spec get_implementation_address_hash_string_eip1167(Hash.Address.t(), any()) :: String.t() | nil
+ def get_implementation_address_hash_string_eip1167(proxy_address_hash, proxy_abi) do
+ get_implementation_address_hash_string_by_module(
+ EIP1167,
+ :get_implementation_address_hash_string_eip1967,
+ [
+ proxy_address_hash,
+ proxy_abi
+ ]
+ )
+ end
+
+ @doc """
+ Returns EIP-1967 implementation address or tries next proxy pattern
+ """
+ @spec get_implementation_address_hash_string_eip1967(Hash.Address.t(), any()) :: String.t() | nil
+ def get_implementation_address_hash_string_eip1967(proxy_address_hash, proxy_abi) do
+ get_implementation_address_hash_string_by_module(
+ EIP1967,
+ :get_implementation_address_hash_string_eip1822,
+ [
+ proxy_address_hash,
+ proxy_abi
+ ]
+ )
+ end
+
+ @doc """
+ Returns EIP-1822 implementation address or tries next proxy pattern
+ """
+ @spec get_implementation_address_hash_string_eip1822(Hash.Address.t(), any()) :: String.t() | nil
+ def get_implementation_address_hash_string_eip1822(proxy_address_hash, proxy_abi) do
+ get_implementation_address_hash_string_by_module(EIP1822, [proxy_address_hash, proxy_abi])
+ end
+
+ defp get_implementation_address_hash_string_by_module(
+ module,
+ next_func \\ :fallback_proxy_detection,
+ [proxy_address_hash, _proxy_abi] = args
+ ) do
+ implementation_address_hash_string = module.get_implementation_address_hash_string(proxy_address_hash)
+
+ if !is_nil(implementation_address_hash_string) && implementation_address_hash_string !== burn_address_hash_string() do
+ implementation_address_hash_string
+ else
+ apply(__MODULE__, next_func, args)
+ end
+ end
+
+ @spec fallback_proxy_detection(Hash.Address.t(), any()) :: String.t() | nil
+ def fallback_proxy_detection(proxy_address_hash, proxy_abi) do
+ implementation_method_abi = get_naive_implementation_abi(proxy_abi, "implementation")
+
+ get_implementation_method_abi = get_naive_implementation_abi(proxy_abi, "getImplementation")
+
+ comptroller_implementation_method_abi = get_naive_implementation_abi(proxy_abi, "comptrollerImplementation")
+
+ master_copy_method_abi = get_master_copy_pattern(proxy_abi)
+
+ get_address_method_abi = get_naive_implementation_abi(proxy_abi, "getAddress")
+
+ cond do
+ implementation_method_abi ->
+ Basic.get_implementation_address_hash_string(@implementation_signature, proxy_address_hash, proxy_abi)
+
+ get_implementation_method_abi ->
+ Basic.get_implementation_address_hash_string(@get_implementation_signature, proxy_address_hash, proxy_abi)
+
+ master_copy_method_abi ->
+ MasterCopy.get_implementation_address_hash_string(proxy_address_hash)
+
+ comptroller_implementation_method_abi ->
+ Basic.get_implementation_address_hash_string(
+ @comptroller_implementation_signature,
+ proxy_address_hash,
+ proxy_abi
+ )
+
+ get_address_method_abi ->
+ EIP930.get_implementation_address_hash_string(@get_address_signature, proxy_address_hash, proxy_abi)
+
+ true ->
+ nil
+ end
+ end
+
+ defp get_naive_implementation_abi(abi, getter_name) do
+ abi
+ |> Enum.find(fn method ->
+ Map.get(method, "name") == getter_name && Map.get(method, "stateMutability") == "view"
+ end)
+ end
+
+ defp get_master_copy_pattern(abi) do
+ abi
+ |> Enum.find(fn method ->
+ master_copy_pattern?(method)
+ end)
+ end
+
+ @doc """
+ Returns combined ABI from proxy and implementation smart-contracts
+ """
+ @spec combine_proxy_implementation_abi(any(), any()) :: SmartContract.abi()
+ def combine_proxy_implementation_abi(smart_contract, options \\ [])
+
+ def combine_proxy_implementation_abi(%SmartContract{abi: abi} = smart_contract, options) when not is_nil(abi) do
+ implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, options)
+
+ if Enum.empty?(implementation_abi), do: abi, else: implementation_abi ++ abi
+ end
+
+ def combine_proxy_implementation_abi(_, _) do
+ []
+ end
+
+ defp find_input_by_name(inputs, name) do
+ inputs
+ |> Enum.find(fn input ->
+ Map.get(input, "name") == name
+ end)
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex
new file mode 100644
index 000000000000..dc4f305900ee
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex
@@ -0,0 +1,45 @@
+defmodule Explorer.Chain.SmartContract.Proxy.Basic do
+ @moduledoc """
+ Module for fetching proxy implementation from specific smart-contract getter
+ """
+
+ alias Explorer.Chain.SmartContract
+ alias Explorer.SmartContract.Reader
+
+ @doc """
+ Gets implementation hash string of proxy contract from getter.
+ """
+ @spec get_implementation_address_hash_string(binary, binary, SmartContract.abi()) :: nil | binary
+ def get_implementation_address_hash_string(signature, proxy_address_hash, abi) do
+ implementation_address =
+ case Reader.query_contract(
+ proxy_address_hash,
+ abi,
+ %{
+ "#{signature}" => []
+ },
+ false
+ ) do
+ %{^signature => {:ok, [result]}} -> result
+ _ -> nil
+ end
+
+ adds_0x_to_address(implementation_address)
+ end
+
+ @doc """
+ Adds 0x to address at the beginning
+ """
+ @spec adds_0x_to_address(nil | binary()) :: nil | binary()
+ def adds_0x_to_address(nil), do: nil
+
+ def adds_0x_to_address(address) do
+ if address do
+ if String.starts_with?(address, "0x") do
+ address
+ else
+ "0x" <> Base.encode16(address, case: :lower)
+ end
+ end
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex
new file mode 100644
index 000000000000..5095c6b17c5e
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex
@@ -0,0 +1,61 @@
+defmodule Explorer.Chain.SmartContract.Proxy.EIP1167 do
+ @moduledoc """
+ Module for fetching proxy implementation from https://eips.ethereum.org/EIPS/eip-1167 (Minimal Proxy Contract)
+ """
+
+ alias Explorer.Chain
+ alias Explorer.Chain.{Address, Hash, SmartContract}
+ alias Explorer.Chain.SmartContract.Proxy
+
+ @doc """
+ Get implementation address following EIP-1167
+ """
+ @spec get_implementation_address(Hash.Address.t(), Keyword.t()) :: SmartContract.t() | nil
+ def get_implementation_address(address_hash, options \\ []) do
+ address_hash
+ |> get_implementation_address_hash_string(options)
+ |> implementation_to_smart_contract(options)
+ end
+
+ @doc """
+ Get implementation address hash string following EIP-1167
+ """
+ @spec get_implementation_address_hash_string(Hash.Address.t(), Keyword.t()) :: String.t() | nil
+ def get_implementation_address_hash_string(address_hash, options \\ []) do
+ case Chain.select_repo(options).get(Address, address_hash) do
+ nil ->
+ nil
+
+ target_address ->
+ contract_code = target_address.contract_code
+
+ case contract_code do
+ %Chain.Data{bytes: contract_code_bytes} ->
+ contract_bytecode = Base.encode16(contract_code_bytes, case: :lower)
+
+ contract_bytecode |> get_proxy_eip_1167() |> Proxy.abi_decode_address_output()
+
+ _ ->
+ nil
+ end
+ end
+ end
+
+ defp get_proxy_eip_1167(contract_bytecode) do
+ case contract_bytecode do
+ "363d3d373d3d3d363d73" <> <> <> _ ->
+ "0x" <> template_address
+
+ _ ->
+ nil
+ end
+ end
+
+ defp implementation_to_smart_contract(nil, _options), do: nil
+
+ defp implementation_to_smart_contract(address_hash, options) do
+ address_hash
+ |> SmartContract.get_smart_contract_query()
+ |> Chain.select_repo(options).one(timeout: 10_000)
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1822.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1822.ex
new file mode 100644
index 000000000000..be89f8080f5c
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1822.ex
@@ -0,0 +1,27 @@
+defmodule Explorer.Chain.SmartContract.Proxy.EIP1822 do
+ @moduledoc """
+ Module for fetching proxy implementation from https://eips.ethereum.org/EIPS/eip-1822 Universal Upgradeable Proxy Standard (UUPS)
+ """
+ alias Explorer.Chain.Hash
+ alias Explorer.Chain.SmartContract.Proxy
+
+ # keccak256("PROXIABLE")
+ @storage_slot_proxiable "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7"
+
+ @doc """
+ Get implementation address hash string following EIP-1822
+ """
+ @spec get_implementation_address_hash_string(Hash.Address.t()) :: nil | binary
+ def get_implementation_address_hash_string(proxy_address_hash) do
+ json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
+
+ proxiable_contract_address_hash_string =
+ Proxy.get_implementation_from_storage(
+ proxy_address_hash,
+ @storage_slot_proxiable,
+ json_rpc_named_arguments
+ )
+
+ Proxy.abi_decode_address_output(proxiable_contract_address_hash_string)
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex
new file mode 100644
index 000000000000..5cc06c75ed9e
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex
@@ -0,0 +1,95 @@
+defmodule Explorer.Chain.SmartContract.Proxy.EIP1967 do
+ @moduledoc """
+ Module for fetching proxy implementation from https://eips.ethereum.org/EIPS/eip-1967 (Proxy Storage Slots)
+ """
+ alias EthereumJSONRPC.Contract
+ alias Explorer.Chain.Hash
+ alias Explorer.Chain.SmartContract.Proxy
+ alias Explorer.Chain.SmartContract.Proxy.Basic
+
+ import Explorer.Chain.SmartContract, only: [is_burn_signature_or_nil: 1]
+
+ # supported signatures:
+ # 5c60da1b = keccak256(implementation())
+ @implementation_signature "5c60da1b"
+
+ @storage_slot_logic_contract_address "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
+
+ # to be precise, it is not the part of the EIP-1967 standard, but still uses the same pattern
+ # changes requested by https://github.com/blockscout/blockscout/issues/5292
+ # This is the keccak-256 hash of "org.zeppelinos.proxy.implementation"
+ @storage_slot_openzeppelin_contract_address "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3"
+
+ @doc """
+ Get implementation address hash string following EIP-1967
+ """
+ @spec get_implementation_address_hash_string(Hash.Address.t()) :: nil | binary
+ def get_implementation_address_hash_string(proxy_address_hash) do
+ json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
+
+ eip1967_implementation_address_hash_string =
+ Proxy.get_implementation_from_storage(
+ proxy_address_hash,
+ @storage_slot_logic_contract_address,
+ json_rpc_named_arguments
+ ) ||
+ fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments)
+
+ implementation_address_hash_string =
+ if eip1967_implementation_address_hash_string do
+ eip1967_implementation_address_hash_string
+ else
+ Proxy.get_implementation_from_storage(
+ proxy_address_hash,
+ @storage_slot_openzeppelin_contract_address,
+ json_rpc_named_arguments
+ )
+ end
+
+ Proxy.abi_decode_address_output(implementation_address_hash_string)
+ end
+
+ # changes requested by https://github.com/blockscout/blockscout/issues/4770
+ # for support BeaconProxy pattern
+ defp fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) do
+ # https://eips.ethereum.org/EIPS/eip-1967
+ storage_slot_beacon_contract_address = "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50"
+
+ implementation_method_abi = [
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}],
+ "name" => "implementation",
+ "inputs" => []
+ }
+ ]
+
+ case Contract.eth_get_storage_at_request(
+ proxy_address_hash,
+ storage_slot_beacon_contract_address,
+ nil,
+ json_rpc_named_arguments
+ ) do
+ {:ok, empty_address}
+ when is_burn_signature_or_nil(empty_address) ->
+ nil
+
+ {:ok, beacon_contract_address} ->
+ case @implementation_signature
+ |> Basic.get_implementation_address_hash_string(
+ beacon_contract_address,
+ implementation_method_abi
+ ) do
+ <> ->
+ implementation_address
+
+ _ ->
+ nil
+ end
+
+ _ ->
+ nil
+ end
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_930.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_930.ex
new file mode 100644
index 000000000000..600128191aed
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_930.ex
@@ -0,0 +1,32 @@
+defmodule Explorer.Chain.SmartContract.Proxy.EIP930 do
+ @moduledoc """
+ Module for fetching proxy implementation from smart-contract getter following https://github.com/ethereum/EIPs/issues/930
+ """
+
+ alias Explorer.Chain.SmartContract
+ alias Explorer.Chain.SmartContract.Proxy.Basic
+ alias Explorer.SmartContract.Reader
+
+ @storage_slot_logic_contract_address "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
+
+ @doc """
+ Gets implementation hash string of proxy contract from getter.
+ """
+ @spec get_implementation_address_hash_string(binary, binary, SmartContract.abi()) :: nil | binary
+ def get_implementation_address_hash_string(signature, proxy_address_hash, abi) do
+ implementation_address =
+ case Reader.query_contract(
+ proxy_address_hash,
+ abi,
+ %{
+ "#{signature}" => [@storage_slot_logic_contract_address]
+ },
+ false
+ ) do
+ %{^signature => {:ok, [result]}} -> result
+ _ -> nil
+ end
+
+ Basic.adds_0x_to_address(implementation_address)
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex
new file mode 100644
index 000000000000..97b927981392
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex
@@ -0,0 +1,41 @@
+defmodule Explorer.Chain.SmartContract.Proxy.MasterCopy do
+ @moduledoc """
+ Module for fetching master-copy proxy implementation
+ """
+
+ alias EthereumJSONRPC.Contract
+ alias Explorer.Chain.Hash
+ alias Explorer.Chain.SmartContract.Proxy
+
+ import Explorer.Chain.SmartContract, only: [is_burn_signature: 1]
+
+ @doc """
+ Gets implementation address hash string for proxy contract from master-copy pattern
+ """
+ @spec get_implementation_address_hash_string(Hash.Address.t()) :: nil | binary
+ def get_implementation_address_hash_string(proxy_address_hash) do
+ json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
+
+ master_copy_storage_pointer = "0x0"
+
+ {:ok, implementation_address} =
+ case Contract.eth_get_storage_at_request(
+ proxy_address_hash,
+ master_copy_storage_pointer,
+ nil,
+ json_rpc_named_arguments
+ ) do
+ {:ok, empty_address}
+ when is_burn_signature(empty_address) ->
+ {:ok, "0x"}
+
+ {:ok, logic_contract_address} ->
+ {:ok, logic_contract_address}
+
+ _ ->
+ {:ok, nil}
+ end
+
+ Proxy.abi_decode_address_output(implementation_address)
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/smart_contract_additional_sources.ex b/apps/explorer/lib/explorer/chain/smart_contract_additional_sources.ex
index 850c28f4b678..bdf2581becd0 100644
--- a/apps/explorer/lib/explorer/chain/smart_contract_additional_sources.ex
+++ b/apps/explorer/lib/explorer/chain/smart_contract_additional_sources.ex
@@ -8,6 +8,8 @@ defmodule Explorer.Chain.SmartContractAdditionalSource do
use Explorer.Schema
+ import Explorer.Chain, only: [select_repo: 1]
+
alias Explorer.Chain.{Hash, SmartContract}
@typedoc """
@@ -59,5 +61,24 @@ defmodule Explorer.Chain.SmartContractAdditionalSource do
add_error(validated, :contract_source_code, error_message(error))
end
+ @doc """
+ Returns all additional sources for the given smart-contract address hash
+ """
+ @spec get_contract_additional_sources(SmartContract.t() | nil, Keyword.t()) :: [__MODULE__.t()]
+ def get_contract_additional_sources(smart_contract, options) do
+ if smart_contract do
+ all_additional_sources_query =
+ from(
+ s in __MODULE__,
+ where: s.address_hash == ^smart_contract.address_hash
+ )
+
+ all_additional_sources_query
+ |> select_repo(options).all()
+ else
+ []
+ end
+ end
+
defp error_message(_), do: "There was an error validating your contract, please try again."
end
diff --git a/apps/explorer/lib/explorer/chain/supply.ex b/apps/explorer/lib/explorer/chain/supply.ex
index b442e4012597..0287a0dde80b 100644
--- a/apps/explorer/lib/explorer/chain/supply.ex
+++ b/apps/explorer/lib/explorer/chain/supply.ex
@@ -7,7 +7,7 @@ defmodule Explorer.Chain.Supply do
"""
@doc """
- The current total number of coins minted minus verifiably burned coins.
+ The current total number of coins minted minus verifiably burnt coins.
"""
@callback total :: non_neg_integer() | %Decimal{sign: 1}
diff --git a/apps/explorer/lib/explorer/chain/supply/exchange_rate.ex b/apps/explorer/lib/explorer/chain/supply/exchange_rate.ex
index d45a8edc02e4..1ef83b1a90c2 100644
--- a/apps/explorer/lib/explorer/chain/supply/exchange_rate.ex
+++ b/apps/explorer/lib/explorer/chain/supply/exchange_rate.ex
@@ -5,7 +5,6 @@ defmodule Explorer.Chain.Supply.ExchangeRate do
use Explorer.Chain.Supply
- alias Explorer.ExchangeRates.Token
alias Explorer.Market
def circulating do
@@ -17,6 +16,6 @@ defmodule Explorer.Chain.Supply.ExchangeRate do
end
def exchange_rate do
- Market.get_exchange_rate(Explorer.coin()) || Token.null()
+ Market.get_coin_exchange_rate()
end
end
diff --git a/apps/explorer/lib/explorer/chain/supply/rsk.ex b/apps/explorer/lib/explorer/chain/supply/rsk.ex
index 1037dd4cf79e..2eb9caa93350 100644
--- a/apps/explorer/lib/explorer/chain/supply/rsk.ex
+++ b/apps/explorer/lib/explorer/chain/supply/rsk.ex
@@ -5,7 +5,7 @@ defmodule Explorer.Chain.Supply.RSK do
use Explorer.Chain.Supply
- import Ecto.Query, only: [from: 2]
+ import Ecto.Query, only: [from: 2, subquery: 1]
import EthereumJSONRPC, only: [integer_to_quantity: 1]
alias EthereumJSONRPC.FetchedBalances
@@ -16,7 +16,9 @@ defmodule Explorer.Chain.Supply.RSK do
@cache_name :rsk_balance
@balance_key :balance
+ @rsk_bridge_contract_address "0x0000000000000000000000000000000001000006"
+ @spec market_cap(any()) :: Decimal.t()
def market_cap(%{usd_value: usd_value}) when not is_nil(usd_value) do
btc = circulating()
@@ -25,31 +27,32 @@ defmodule Explorer.Chain.Supply.RSK do
def market_cap(_), do: Decimal.new(0)
- @doc "Equivalent to getting the circulating value "
+ @doc "Equivalent to getting the circulating value"
def supply_for_days(days) do
now = Timex.now()
- balances_query =
+ base_query =
from(balance in CoinBalance,
join: block in Block,
on: block.number == balance.block_number,
where: block.consensus == true,
- where: balance.address_hash == ^"0x0000000000000000000000000000000001000006",
- where: block.timestamp > ^Timex.shift(now, days: -days),
- distinct: fragment("date_trunc('day', ?)", block.timestamp),
- select: {block.timestamp, balance.value}
+ where: balance.address_hash == ^@rsk_bridge_contract_address,
+ select: %{timestamp: block.timestamp, value: balance.value}
+ )
+
+ balances_query =
+ from(q in subquery(base_query),
+ where: q.timestamp > ^Timex.shift(now, days: -days),
+ distinct: fragment("date_trunc('day', ?)", q.timestamp),
+ select: {q.timestamp, q.value}
)
balance_before_query =
- from(balance in CoinBalance,
- join: block in Block,
- on: block.number == balance.block_number,
- where: block.consensus == true,
- where: balance.address_hash == ^"0x0000000000000000000000000000000001000006",
- where: block.timestamp <= ^Timex.shift(Timex.now(), days: -days),
- order_by: [desc: block.timestamp],
+ from(q in subquery(base_query),
+ where: q.timestamp <= ^Timex.shift(Timex.now(), days: -days),
+ order_by: [desc: q.timestamp],
limit: 1,
- select: balance.value
+ select: q.value
)
by_day =
@@ -105,7 +108,7 @@ defmodule Explorer.Chain.Supply.RSK do
max_number = BlockNumber.get_max()
params = [
- %{block_quantity: integer_to_quantity(max_number), hash_data: "0x0000000000000000000000000000000001000006"}
+ %{block_quantity: integer_to_quantity(max_number), hash_data: @rsk_bridge_contract_address}
]
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
@@ -116,7 +119,7 @@ defmodule Explorer.Chain.Supply.RSK do
errors: [],
params_list: [
%{
- address_hash: "0x0000000000000000000000000000000001000006",
+ address_hash: @rsk_bridge_contract_address,
value: value
}
]
diff --git a/apps/explorer/lib/explorer/chain/token.ex b/apps/explorer/lib/explorer/chain/token.ex
index a2c891a9d85d..66c3fdd5e2de 100644
--- a/apps/explorer/lib/explorer/chain/token.ex
+++ b/apps/explorer/lib/explorer/chain/token.ex
@@ -23,9 +23,18 @@ defmodule Explorer.Chain.Token do
import Ecto.{Changeset, Query}
alias Ecto.Changeset
- alias Explorer.Chain.{Address, Hash, Token}
+ alias Explorer.{Chain, SortingHelper}
+ alias Explorer.Chain.{Address, Hash, Search, Token}
alias Explorer.SmartContract.Helper
+ @default_sorting [
+ desc_nulls_last: :circulating_market_cap,
+ desc_nulls_last: :fiat_value,
+ desc_nulls_last: :holder_count,
+ asc: :name,
+ asc: :contract_address_hash
+ ]
+
@typedoc """
* `name` - Name of the token
* `symbol` - Trading symbol of the token
@@ -40,6 +49,7 @@ defmodule Explorer.Chain.Token do
* `fiat_value` - The price of a token in a configured currency (USD by default).
* `circulating_market_cap` - The circulating market cap of a token in a configured currency (USD by default).
* `icon_url` - URL of the token's icon.
+ * `is_verified_via_admin_panel` - is token verified via admin panel.
"""
@type t :: %Token{
name: String.t(),
@@ -55,7 +65,8 @@ defmodule Explorer.Chain.Token do
total_supply_updated_at_block: non_neg_integer() | nil,
fiat_value: Decimal.t() | nil,
circulating_market_cap: Decimal.t() | nil,
- icon_url: String.t()
+ icon_url: String.t(),
+ is_verified_via_admin_panel: boolean()
}
@derive {Poison.Encoder,
@@ -88,6 +99,7 @@ defmodule Explorer.Chain.Token do
field(:fiat_value, :decimal)
field(:circulating_market_cap, :decimal)
field(:icon_url, :string)
+ field(:is_verified_via_admin_panel, :boolean)
belongs_to(
:contract_address,
@@ -102,14 +114,13 @@ defmodule Explorer.Chain.Token do
end
@required_attrs ~w(contract_address_hash type)a
- @optional_attrs ~w(cataloged decimals name symbol total_supply skip_metadata total_supply_updated_at_block updated_at fiat_value circulating_market_cap icon_url)a
+ @optional_attrs ~w(cataloged decimals name symbol total_supply skip_metadata total_supply_updated_at_block updated_at fiat_value circulating_market_cap icon_url is_verified_via_admin_panel)a
@doc false
def changeset(%Token{} = token, params \\ %{}) do
token
|> cast(params, @required_attrs ++ @optional_attrs)
|> validate_required(@required_attrs)
- |> foreign_key_constraint(:contract_address)
|> trim_name()
|> sanitize_token_input(:name)
|> sanitize_token_input(:symbol)
@@ -153,24 +164,52 @@ defmodule Explorer.Chain.Token do
)
end
+ def tokens_by_contract_address_hashes(contract_address_hashes) do
+ from(token in __MODULE__, where: token.contract_address_hash in ^contract_address_hashes)
+ end
+
@doc """
- Builds an `Ecto.Query` to fetch a `batch_size` number of the tokens,
- possibly starting from `last_updated_address_hash` ordered by `contract_address_hash`.
+ Lists the top `t:__MODULE__.t/0`'s'.
"""
- def tokens_to_update_fiat_value(nil, batch_size) do
- from(
- token in __MODULE__,
- order_by: token.contract_address_hash,
- limit: ^batch_size
- )
+ @spec list_top(String.t() | nil, [
+ Chain.paging_options()
+ | {:sorting, SortingHelper.sorting_params()}
+ | {:token_type, [String.t()]}
+ ]) :: [Token.t()]
+ def list_top(filter, options \\ []) do
+ paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options())
+ token_type = Keyword.get(options, :token_type, nil)
+ sorting = Keyword.get(options, :sorting, [])
+
+ query = from(t in Token, preload: [:contract_address])
+
+ sorted_paginated_query =
+ query
+ |> apply_filter(token_type)
+ |> SortingHelper.apply_sorting(sorting, @default_sorting)
+ |> SortingHelper.page_with_sorting(paging_options, sorting, @default_sorting)
+
+ filtered_query =
+ case filter && filter !== "" && Search.prepare_search_term(filter) do
+ {:some, filter_term} ->
+ sorted_paginated_query
+ |> where(fragment("to_tsvector('english', symbol || ' ' || name) @@ to_tsquery(?)", ^filter_term))
+
+ _ ->
+ sorted_paginated_query
+ end
+
+ filtered_query
+ |> Chain.select_repo(options).all()
end
- def tokens_to_update_fiat_value(last_updated_address_hash, batch_size) do
- from(
- token in __MODULE__,
- order_by: token.contract_address_hash,
- where: token.contract_address_hash > ^last_updated_address_hash,
- limit: ^batch_size
- )
+ defp apply_filter(query, empty_type) when empty_type in [nil, []], do: query
+
+ defp apply_filter(query, token_types) when is_list(token_types) do
+ from(t in query, where: t.type in ^token_types)
+ end
+
+ def get_by_contract_address_hash(hash, options) do
+ Chain.select_repo(options).get_by(__MODULE__, contract_address_hash: hash)
end
end
diff --git a/apps/explorer/lib/explorer/chain/token/instance.ex b/apps/explorer/lib/explorer/chain/token/instance.ex
index ea91796e3214..43249f7c722b 100644
--- a/apps/explorer/lib/explorer/chain/token/instance.ex
+++ b/apps/explorer/lib/explorer/chain/token/instance.ex
@@ -5,7 +5,9 @@ defmodule Explorer.Chain.Token.Instance do
use Explorer.Schema
- alias Explorer.Chain.{Address, Hash, Token, TokenTransfer}
+ alias Explorer.{Chain, Helper}
+ alias Explorer.Chain.{Address, Block, Hash, Token, TokenTransfer}
+ alias Explorer.Chain.Address.CurrentTokenBalance
alias Explorer.Chain.Token.Instance
alias Explorer.PagingOptions
@@ -20,7 +22,12 @@ defmodule Explorer.Chain.Token.Instance do
token_id: non_neg_integer(),
token_contract_address_hash: Hash.Address.t(),
metadata: map() | nil,
- error: String.t()
+ error: String.t(),
+ owner_address_hash: Hash.Address.t(),
+ owner_updated_at_block: Block.block_number(),
+ owner_updated_at_log_index: non_neg_integer(),
+ current_token_balance: any(),
+ is_unique: bool() | nil
}
@primary_key false
@@ -28,8 +35,12 @@ defmodule Explorer.Chain.Token.Instance do
field(:token_id, :decimal, primary_key: true)
field(:metadata, :map)
field(:error, :string)
+ field(:owner_updated_at_block, :integer)
+ field(:owner_updated_at_log_index, :integer)
+ field(:current_token_balance, :any, virtual: true)
+ field(:is_unique, :boolean, virtual: true)
- belongs_to(:owner, Address, references: :hash, define_field: false)
+ belongs_to(:owner, Address, foreign_key: :owner_address_hash, references: :hash, type: Hash.Address)
belongs_to(
:token,
@@ -45,7 +56,15 @@ defmodule Explorer.Chain.Token.Instance do
def changeset(%Instance{} = instance, params \\ %{}) do
instance
- |> cast(params, [:token_id, :metadata, :token_contract_address_hash, :error])
+ |> cast(params, [
+ :token_id,
+ :metadata,
+ :token_contract_address_hash,
+ :error,
+ :owner_address_hash,
+ :owner_updated_at_block,
+ :owner_updated_at_log_index
+ ])
|> validate_required([:token_id, :token_contract_address_hash])
|> foreign_key_constraint(:token_contract_address_hash)
end
@@ -78,19 +97,374 @@ defmodule Explorer.Chain.Token.Instance do
def page_token_instance(query, _), do: query
def owner_query(%Instance{token_contract_address_hash: token_contract_address_hash, token_id: token_id}) do
- from(
- tt in TokenTransfer,
- join: to_address in assoc(tt, :to_address),
- where:
- tt.token_contract_address_hash == ^token_contract_address_hash and
- fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^token_id),
- order_by: [desc: tt.block_number],
- limit: 1,
- select: to_address
+ CurrentTokenBalance
+ |> where(
+ [ctb],
+ ctb.token_contract_address_hash == ^token_contract_address_hash and ctb.token_id == ^token_id and ctb.value > 0
)
+ |> limit(1)
+ |> select([ctb], ctb.address_hash)
end
@spec token_instance_query(non_neg_integer(), Hash.Address.t()) :: Ecto.Query.t()
def token_instance_query(token_id, token_contract_address),
do: from(i in Instance, where: i.token_contract_address_hash == ^token_contract_address and i.token_id == ^token_id)
+
+ @spec nft_list(binary() | Hash.Address.t(), keyword()) :: [Instance.t()]
+ def nft_list(address_hash, options \\ [])
+
+ def nft_list(address_hash, options) when is_list(options) do
+ nft_list(address_hash, Keyword.get(options, :token_type, []), options)
+ end
+
+ defp nft_list(address_hash, ["ERC-721"], options) do
+ erc_721_token_instances_by_owner_address_hash(address_hash, options)
+ end
+
+ defp nft_list(address_hash, ["ERC-1155"], options) do
+ erc_1155_token_instances_by_address_hash(address_hash, options)
+ end
+
+ defp nft_list(address_hash, _, options) do
+ paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options())
+
+ case paging_options do
+ %PagingOptions{key: {_contract_address_hash, _token_id, "ERC-1155"}} ->
+ erc_1155_token_instances_by_address_hash(address_hash, options)
+
+ _ ->
+ erc_721 = erc_721_token_instances_by_owner_address_hash(address_hash, options)
+
+ if length(erc_721) == paging_options.page_size do
+ erc_721
+ else
+ erc_1155 = erc_1155_token_instances_by_address_hash(address_hash, options)
+
+ (erc_721 ++ erc_1155) |> Enum.take(paging_options.page_size)
+ end
+ end
+ end
+
+ @doc """
+ In this function used fact that only ERC-721 instances has NOT NULL owner_address_hash.
+ """
+ @spec erc_721_token_instances_by_owner_address_hash(binary() | Hash.Address.t(), keyword) :: [Instance.t()]
+ def erc_721_token_instances_by_owner_address_hash(address_hash, options \\ []) do
+ paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options())
+ necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
+
+ __MODULE__
+ |> where([ti], ti.owner_address_hash == ^address_hash)
+ |> order_by([ti], asc: ti.token_contract_address_hash, desc: ti.token_id)
+ |> limit(^paging_options.page_size)
+ |> page_erc_721_token_instances(paging_options)
+ |> Chain.join_associations(necessity_by_association)
+ |> Chain.select_repo(options).all()
+ end
+
+ defp page_erc_721_token_instances(query, %PagingOptions{key: {contract_address_hash, token_id, "ERC-721"}}) do
+ page_token_instance(query, contract_address_hash, token_id)
+ end
+
+ defp page_erc_721_token_instances(query, _), do: query
+
+ @spec erc_1155_token_instances_by_address_hash(binary() | Hash.Address.t(), keyword) :: [Instance.t()]
+ def erc_1155_token_instances_by_address_hash(address_hash, options \\ []) do
+ paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options())
+ necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
+
+ __MODULE__
+ |> join(:inner, [ti], ctb in CurrentTokenBalance,
+ as: :ctb,
+ on:
+ ctb.token_contract_address_hash == ti.token_contract_address_hash and ctb.token_id == ti.token_id and
+ ctb.address_hash == ^address_hash
+ )
+ |> where([ctb: ctb], ctb.value > 0 and ctb.token_type == "ERC-1155")
+ |> order_by([ti], asc: ti.token_contract_address_hash, desc: ti.token_id)
+ |> limit(^paging_options.page_size)
+ |> page_erc_1155_token_instances(paging_options)
+ |> select_merge([ctb: ctb], %{current_token_balance: ctb})
+ |> Chain.join_associations(necessity_by_association)
+ |> Chain.select_repo(options).all()
+ end
+
+ defp page_erc_1155_token_instances(query, %PagingOptions{key: {contract_address_hash, token_id, "ERC-1155"}}) do
+ page_token_instance(query, contract_address_hash, token_id)
+ end
+
+ defp page_erc_1155_token_instances(query, _), do: query
+
+ defp page_token_instance(query, contract_address_hash, token_id) do
+ query
+ |> where(
+ [ti],
+ ti.token_contract_address_hash > ^contract_address_hash or
+ (ti.token_contract_address_hash == ^contract_address_hash and ti.token_id < ^token_id)
+ )
+ end
+
+ @doc """
+ Function to be used in BlockScoutWeb.Chain.next_page_params/4
+ """
+ @spec nft_list_next_page_params(Explorer.Chain.Token.Instance.t()) :: %{binary() => any}
+ def nft_list_next_page_params(%__MODULE__{
+ current_token_balance: %CurrentTokenBalance{},
+ token_contract_address_hash: token_contract_address_hash,
+ token_id: token_id
+ }) do
+ %{"token_contract_address_hash" => token_contract_address_hash, "token_id" => token_id, "token_type" => "ERC-1155"}
+ end
+
+ def nft_list_next_page_params(%__MODULE__{
+ token_contract_address_hash: token_contract_address_hash,
+ token_id: token_id
+ }) do
+ %{"token_contract_address_hash" => token_contract_address_hash, "token_id" => token_id, "token_type" => "ERC-721"}
+ end
+
+ @preloaded_nfts_limit 9
+
+ @spec nft_collections(binary() | Hash.Address.t(), keyword) :: list
+ def nft_collections(address_hash, options \\ [])
+
+ def nft_collections(address_hash, options) when is_list(options) do
+ nft_collections(address_hash, Keyword.get(options, :token_type, []), options)
+ end
+
+ defp nft_collections(address_hash, ["ERC-721"], options) do
+ erc_721_collections_by_address_hash(address_hash, options)
+ end
+
+ defp nft_collections(address_hash, ["ERC-1155"], options) do
+ erc_1155_collections_by_address_hash(address_hash, options)
+ end
+
+ defp nft_collections(address_hash, _, options) do
+ paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options())
+
+ case paging_options do
+ %PagingOptions{key: {_contract_address_hash, "ERC-1155"}} ->
+ erc_1155_collections_by_address_hash(address_hash, options)
+
+ _ ->
+ erc_721 = erc_721_collections_by_address_hash(address_hash, options)
+
+ if length(erc_721) == paging_options.page_size do
+ erc_721
+ else
+ erc_1155 = erc_1155_collections_by_address_hash(address_hash, options)
+
+ (erc_721 ++ erc_1155) |> Enum.take(paging_options.page_size)
+ end
+ end
+ end
+
+ @spec erc_721_collections_by_address_hash(binary() | Hash.Address.t(), keyword) :: [CurrentTokenBalance.t()]
+ def erc_721_collections_by_address_hash(address_hash, options) do
+ paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options())
+ necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
+
+ CurrentTokenBalance
+ |> where([ctb], ctb.address_hash == ^address_hash and ctb.value > 0 and ctb.token_type == "ERC-721")
+ |> order_by([ctb], asc: ctb.token_contract_address_hash)
+ |> page_erc_721_nft_collections(paging_options)
+ |> limit(^paging_options.page_size)
+ |> Chain.join_associations(necessity_by_association)
+ |> Chain.select_repo(options).all()
+ |> Enum.map(&erc_721_preload_nft(&1, options))
+ end
+
+ defp page_erc_721_nft_collections(query, %PagingOptions{key: {contract_address_hash, "ERC-721"}}) do
+ page_nft_collections(query, contract_address_hash)
+ end
+
+ defp page_erc_721_nft_collections(query, _), do: query
+
+ @spec erc_1155_collections_by_address_hash(binary() | Hash.Address.t(), keyword) :: [
+ %{
+ token_contract_address_hash: Hash.Address.t(),
+ distinct_token_instances_count: integer(),
+ token_ids: [integer()]
+ }
+ ]
+ def erc_1155_collections_by_address_hash(address_hash, options) do
+ paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options())
+
+ CurrentTokenBalance
+ |> where([ctb], ctb.address_hash == ^address_hash and ctb.value > 0 and ctb.token_type == "ERC-1155")
+ |> group_by([ctb], ctb.token_contract_address_hash)
+ |> order_by([ctb], asc: ctb.token_contract_address_hash)
+ |> select([ctb], %{
+ token_contract_address_hash: ctb.token_contract_address_hash,
+ distinct_token_instances_count: fragment("COUNT(*)"),
+ token_ids: fragment("array_agg(?)", ctb.token_id)
+ })
+ |> page_erc_1155_nft_collections(paging_options)
+ |> limit(^paging_options.page_size)
+ |> Chain.select_repo(options).all()
+ |> Enum.map(&erc_1155_preload_nft(&1, address_hash, options))
+ |> Helper.custom_preload(options, Token, :token_contract_address_hash, :contract_address_hash, :token)
+ end
+
+ defp page_erc_1155_nft_collections(query, %PagingOptions{key: {contract_address_hash, "ERC-1155"}}) do
+ page_nft_collections(query, contract_address_hash)
+ end
+
+ defp page_erc_1155_nft_collections(query, _), do: query
+
+ defp page_nft_collections(query, token_contract_address_hash) do
+ query
+ |> where([ctb], ctb.token_contract_address_hash > ^token_contract_address_hash)
+ end
+
+ defp erc_721_preload_nft(
+ %CurrentTokenBalance{token_contract_address_hash: token_contract_address_hash, address_hash: address_hash} =
+ ctb,
+ options
+ ) do
+ instances =
+ Instance
+ |> where(
+ [ti],
+ ti.token_contract_address_hash == ^token_contract_address_hash and ti.owner_address_hash == ^address_hash
+ )
+ |> order_by([ti], desc: ti.token_id)
+ |> limit(^@preloaded_nfts_limit)
+ |> Chain.select_repo(options).all()
+
+ %CurrentTokenBalance{ctb | preloaded_token_instances: instances}
+ end
+
+ defp erc_1155_preload_nft(
+ %{token_contract_address_hash: token_contract_address_hash, token_ids: token_ids} = collection,
+ address_hash,
+ options
+ ) do
+ token_ids = token_ids |> Enum.sort(:desc) |> Enum.take(@preloaded_nfts_limit)
+
+ instances =
+ Instance
+ |> where([ti], ti.token_contract_address_hash == ^token_contract_address_hash and ti.token_id in ^token_ids)
+ |> join(:inner, [ti], ctb in CurrentTokenBalance,
+ as: :ctb,
+ on:
+ ctb.token_contract_address_hash == ti.token_contract_address_hash and ti.token_id == ctb.token_id and
+ ctb.address_hash == ^address_hash
+ )
+ |> limit(^@preloaded_nfts_limit)
+ |> select_merge([ctb: ctb], %{current_token_balance: ctb})
+ |> Chain.select_repo(options).all()
+ |> Enum.sort_by(& &1.token_id, :desc)
+
+ Map.put(collection, :preloaded_token_instances, instances)
+ end
+
+ @doc """
+ Function to be used in BlockScoutWeb.Chain.next_page_params/4
+ """
+ @spec nft_collections_next_page_params(%{:token_contract_address_hash => any, optional(any) => any}) :: %{
+ binary() => any
+ }
+ def nft_collections_next_page_params(%{
+ token_contract_address_hash: token_contract_address_hash,
+ token: %Token{type: token_type}
+ }) do
+ %{"token_contract_address_hash" => token_contract_address_hash, "token_type" => token_type}
+ end
+
+ def nft_collections_next_page_params(%{
+ token_contract_address_hash: token_contract_address_hash,
+ token_type: token_type
+ }) do
+ %{"token_contract_address_hash" => token_contract_address_hash, "token_type" => token_type}
+ end
+
+ @spec token_instances_by_holder_address_hash(Token.t(), binary() | Hash.Address.t(), keyword) :: [Instance.t()]
+ def token_instances_by_holder_address_hash(token, holder_address_hash, options \\ [])
+
+ def token_instances_by_holder_address_hash(%Token{type: "ERC-721"} = token, holder_address_hash, options) do
+ paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options())
+
+ token.contract_address_hash
+ |> address_to_unique_token_instances()
+ |> where([ti], ti.owner_address_hash == ^holder_address_hash)
+ |> limit(^paging_options.page_size)
+ |> page_token_instance(paging_options)
+ |> Chain.select_repo(options).all()
+ |> Enum.map(&put_is_unique(&1, token, options))
+ end
+
+ def token_instances_by_holder_address_hash(%Token{} = token, holder_address_hash, options) do
+ paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options())
+
+ __MODULE__
+ |> where([ti], ti.token_contract_address_hash == ^token.contract_address_hash)
+ |> join(:inner, [ti], ctb in CurrentTokenBalance,
+ as: :ctb,
+ on:
+ ctb.token_contract_address_hash == ti.token_contract_address_hash and ctb.token_id == ti.token_id and
+ ctb.address_hash == ^holder_address_hash
+ )
+ |> where([ctb: ctb], ctb.value > 0)
+ |> order_by([ti], desc: ti.token_id)
+ |> limit(^paging_options.page_size)
+ |> page_token_instance(paging_options)
+ |> select_merge([ctb: ctb], %{current_token_balance: ctb})
+ |> Chain.select_repo(options).all()
+ |> Enum.map(&put_is_unique(&1, token, options))
+ end
+
+ @doc """
+ Finds token instances (pairs of contract_address_hash and token_id) which was met in token transfers but has no corresponding entry in token_instances table
+ """
+ @spec not_inserted_token_instances_query(integer()) :: Ecto.Query.t()
+ def not_inserted_token_instances_query(limit) do
+ token_transfers_query =
+ TokenTransfer
+ |> where([token_transfer], not is_nil(token_transfer.token_ids) and token_transfer.token_ids != ^[])
+ |> select([token_transfer], %{
+ token_contract_address_hash: token_transfer.token_contract_address_hash,
+ token_id: fragment("unnest(?)", token_transfer.token_ids)
+ })
+
+ token_transfers_query
+ |> subquery()
+ |> join(:left, [token_transfer], token_instance in __MODULE__,
+ on:
+ token_instance.token_contract_address_hash == token_transfer.token_contract_address_hash and
+ token_instance.token_id == token_transfer.token_id
+ )
+ |> where([token_transfer, token_instance], is_nil(token_instance.token_id))
+ |> select([token_transfer, token_instance], %{
+ contract_address_hash: token_transfer.token_contract_address_hash,
+ token_id: token_transfer.token_id
+ })
+ |> limit(^limit)
+ end
+
+ def put_is_unique(instance, token, options) do
+ %__MODULE__{instance | is_unique: is_unique?(instance, token, options)}
+ end
+
+ defp is_unique?(
+ %Instance{current_token_balance: %CurrentTokenBalance{value: %Decimal{} = value}} = instance,
+ token,
+ options
+ ) do
+ if Decimal.compare(value, 1) == :gt do
+ false
+ else
+ is_unique?(%Instance{instance | current_token_balance: nil}, token, options)
+ end
+ end
+
+ defp is_unique?(%Instance{current_token_balance: %CurrentTokenBalance{value: value}}, _token, _options)
+ when value > 1,
+ do: false
+
+ defp is_unique?(instance, token, options),
+ do:
+ not (token.type == "ERC-1155") or
+ Chain.token_id_1155_is_unique?(token.contract_address_hash, instance.token_id, options)
end
diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex
index b70f5623a921..38dad6123521 100644
--- a/apps/explorer/lib/explorer/chain/token_transfer.ex
+++ b/apps/explorer/lib/explorer/chain/token_transfer.ex
@@ -28,7 +28,7 @@ defmodule Explorer.Chain.TokenTransfer do
import Ecto.Query, only: [from: 2, limit: 2, where: 3, join: 5, order_by: 3, preload: 3]
alias Explorer.Chain
- alias Explorer.Chain.{Address, Block, Hash, TokenTransfer, Transaction}
+ alias Explorer.Chain.{Address, Block, DenormalizationHelper, Hash, Log, TokenTransfer, Transaction}
alias Explorer.Chain.Token.Instance
alias Explorer.{PagingOptions, Repo}
@@ -44,10 +44,9 @@ defmodule Explorer.Chain.TokenTransfer do
* `:to_address_hash` - Address hash foreign key
* `:token_contract_address` - The `t:Explorer.Chain.Address.t/0` of the token's contract.
* `:token_contract_address_hash` - Address hash foreign key
- * `:token_id` - ID of the token (applicable to ERC-721 tokens)
* `:transaction` - The `t:Explorer.Chain.Transaction.t/0` ledger
* `:transaction_hash` - Transaction foreign key
- * `:log_index` - Index of the corresponding `t:Explorer.Chain.Log.t/0` in the transaction.
+ * `:log_index` - Index of the corresponding `t:Explorer.Chain.Log.t/0` in the block.
* `:amounts` - Tokens transferred amounts in case of batched transfer in ERC-1155
* `:token_ids` - IDs of the tokens (applicable to ERC-1155 tokens)
"""
@@ -61,7 +60,6 @@ defmodule Explorer.Chain.TokenTransfer do
to_address_hash: Hash.Address.t(),
token_contract_address: %Ecto.Association.NotLoaded{} | Address.t(),
token_contract_address_hash: Hash.Address.t(),
- token_id: non_neg_integer() | nil,
transaction: %Ecto.Association.NotLoaded{} | Transaction.t(),
transaction_hash: Hash.Full.t(),
log_index: non_neg_integer(),
@@ -74,6 +72,8 @@ defmodule Explorer.Chain.TokenTransfer do
@typep api? :: {:api?, true | false}
@constant "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
+ @weth_deposit_signature "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"
+ @weth_withdrawal_signature "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65"
@erc1155_single_transfer_signature "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62"
@erc1155_batch_transfer_signature "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb"
@@ -84,7 +84,6 @@ defmodule Explorer.Chain.TokenTransfer do
field(:amount, :decimal)
field(:block_number, :integer)
field(:log_index, :integer, primary_key: true)
- field(:token_id, :decimal)
field(:amounts, {:array, :decimal})
field(:token_ids, {:array, :decimal})
field(:index_in_batch, :integer, virtual: true)
@@ -127,16 +126,13 @@ defmodule Explorer.Chain.TokenTransfer do
end
@required_attrs ~w(block_number log_index from_address_hash to_address_hash token_contract_address_hash transaction_hash block_hash)a
- @optional_attrs ~w(amount token_id amounts token_ids)a
+ @optional_attrs ~w(amount amounts token_ids)a
@doc false
def changeset(%TokenTransfer{} = struct, params \\ %{}) do
struct
|> cast(params, @required_attrs ++ @optional_attrs)
|> validate_required(@required_attrs)
- |> foreign_key_constraint(:from_address)
- |> foreign_key_constraint(:to_address)
- |> foreign_key_constraint(:token_contract_address)
|> foreign_key_constraint(:transaction)
end
@@ -146,6 +142,10 @@ defmodule Explorer.Chain.TokenTransfer do
"""
def constant, do: @constant
+ def weth_deposit_signature, do: @weth_deposit_signature
+
+ def weth_withdrawal_signature, do: @weth_withdrawal_signature
+
def erc1155_single_transfer_signature, do: @erc1155_single_transfer_signature
def erc1155_batch_transfer_signature, do: @erc1155_batch_transfer_signature
@@ -158,12 +158,13 @@ defmodule Explorer.Chain.TokenTransfer do
@spec fetch_token_transfers_from_token_hash(Hash.t(), [paging_options | api?]) :: []
def fetch_token_transfers_from_token_hash(token_address_hash, options) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
+ preloads = DenormalizationHelper.extend_transaction_preload([:transaction, :token, :from_address, :to_address])
query =
from(
tt in TokenTransfer,
where: tt.token_contract_address_hash == ^token_address_hash and not is_nil(tt.block_number),
- preload: [{:transaction, :block}, :token, :from_address, :to_address],
+ preload: ^preloads,
order_by: [desc: tt.block_number, desc: tt.log_index]
)
@@ -176,6 +177,7 @@ defmodule Explorer.Chain.TokenTransfer do
@spec fetch_token_transfers_from_token_hash_and_token_id(Hash.t(), non_neg_integer(), [paging_options | api?]) :: []
def fetch_token_transfers_from_token_hash_and_token_id(token_address_hash, token_id, options) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
+ preloads = DenormalizationHelper.extend_transaction_preload([:transaction, :token, :from_address, :to_address])
query =
from(
@@ -183,7 +185,7 @@ defmodule Explorer.Chain.TokenTransfer do
where: tt.token_contract_address_hash == ^token_address_hash,
where: fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^Decimal.new(token_id)),
where: not is_nil(tt.block_number),
- preload: [{:transaction, :block}, :token, :from_address, :to_address],
+ preload: ^preloads,
order_by: [desc: tt.block_number, desc: tt.log_index]
)
@@ -357,4 +359,39 @@ defmodule Explorer.Chain.TokenTransfer do
end
def filter_by_type(query, _), do: query
+
+ def only_consensus_transfers_query do
+ from(token_transfer in __MODULE__,
+ inner_join: block in Block,
+ on: token_transfer.block_hash == block.hash,
+ where: block.consensus == true
+ )
+ end
+
+ @doc """
+ Returns a list of block numbers token transfer `t:Log.t/0`s that don't have an
+ associated `t:TokenTransfer.t/0` record.
+ """
+ @spec uncataloged_token_transfer_block_numbers :: {:ok, [non_neg_integer()]}
+ def uncataloged_token_transfer_block_numbers do
+ query =
+ from(l in Log,
+ as: :log,
+ where:
+ l.first_topic == ^@constant or
+ l.first_topic == ^@erc1155_single_transfer_signature or
+ l.first_topic == ^@erc1155_batch_transfer_signature,
+ where:
+ not exists(
+ from(tf in TokenTransfer,
+ where: tf.transaction_hash == parent_as(:log).transaction_hash,
+ where: tf.log_index == parent_as(:log).index
+ )
+ ),
+ select: l.block_number,
+ distinct: l.block_number
+ )
+
+ Repo.stream_reduce(query, [], &[&1 | &2])
+ end
end
diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex
index f088e82aedf0..10b730d6bcd7 100644
--- a/apps/explorer/lib/explorer/chain/transaction.ex
+++ b/apps/explorer/lib/explorer/chain/transaction.ex
@@ -5,38 +5,46 @@ defmodule Explorer.Chain.Transaction do
require Logger
- import Ecto.Query, only: [from: 2, preload: 3, subquery: 1, where: 3]
-
alias ABI.FunctionSelector
alias Ecto.Association.NotLoaded
alias Ecto.Changeset
- alias Explorer.Chain
+ alias Explorer.{Chain, Repo}
alias Explorer.Chain.{
Address,
Block,
ContractMethod,
Data,
+ DenormalizationHelper,
Gas,
Hash,
InternalTransaction,
Log,
SmartContract,
+ Token,
TokenTransfer,
Transaction,
TransactionAction,
Wei
}
+ alias Explorer.Chain.Block.Reward
+ alias Explorer.Chain.SmartContract.Proxy
alias Explorer.Chain.Transaction.{Fork, Status}
+ alias Explorer.Chain.Zkevm.BatchTransaction
+ alias Explorer.{PagingOptions, SortingHelper}
alias Explorer.SmartContract.SigProviderInterface
- @optional_attrs ~w(max_priority_fee_per_gas max_fee_per_gas block_hash block_number created_contract_address_hash cumulative_gas_used earliest_processing_start
- error gas_used index created_contract_code_indexed_at status to_address_hash revert_reason type has_error_in_internal_txs)a
+ @optional_attrs ~w(max_priority_fee_per_gas max_fee_per_gas block_hash block_number block_consensus block_timestamp created_contract_address_hash cumulative_gas_used earliest_processing_start
+ error gas_price gas_used index created_contract_code_indexed_at status to_address_hash revert_reason type has_error_in_internal_txs)a
+
+ @suave_optional_attrs ~w(execution_node_hash wrapped_type wrapped_nonce wrapped_to_address_hash wrapped_gas wrapped_gas_price wrapped_max_priority_fee_per_gas wrapped_max_fee_per_gas wrapped_value wrapped_input wrapped_v wrapped_r wrapped_s wrapped_hash)a
- @required_attrs ~w(from_address_hash gas gas_price hash input nonce r s v value)a
+ @required_attrs ~w(from_address_hash gas hash input nonce r s v value)a
+
+ @empty_attrs ~w()a
@typedoc """
X coordinate module n in
@@ -84,6 +92,8 @@ defmodule Explorer.Chain.Transaction do
`uncles` in one of the `forks`.
* `block_number` - Denormalized `block` `number`. `nil` when transaction is pending or has only been collated into
one of the `uncles` in one of the `forks`.
+ * `block_consensus` - consensus of the block where transaction collated.
+ * `block_timestamp` - timestamp of the block where transaction collated.
* `created_contract_address` - belongs_to association to `address` corresponding to `created_contract_address_hash`.
* `created_contract_address_hash` - Denormalized `internal_transaction` `created_contract_address_hash`
populated only when `to_address_hash` is nil.
@@ -139,47 +149,95 @@ defmodule Explorer.Chain.Transaction do
* `max_fee_per_gas` - Maximum total amount per unit of gas a user is willing to pay for a transaction, including base fee and priority fee.
* `type` - New transaction type identifier introduced in EIP 2718 (Berlin HF)
* `has_error_in_internal_txs` - shows if the internal transactions related to transaction have errors
+ * `execution_node` - execution node address (used by Suave)
+ * `execution_node_hash` - foreign key of `execution_node` (used by Suave)
+ * `wrapped_type` - transaction type from the `wrapped` field (used by Suave)
+ * `wrapped_nonce` - nonce from the `wrapped` field (used by Suave)
+ * `wrapped_to_address` - target address from the `wrapped` field (used by Suave)
+ * `wrapped_to_address_hash` - `wrapped_to_address` foreign key (used by Suave)
+ * `wrapped_gas` - gas from the `wrapped` field (used by Suave)
+ * `wrapped_gas_price` - gas_price from the `wrapped` field (used by Suave)
+ * `wrapped_max_priority_fee_per_gas` - max_priority_fee_per_gas from the `wrapped` field (used by Suave)
+ * `wrapped_max_fee_per_gas` - max_fee_per_gas from the `wrapped` field (used by Suave)
+ * `wrapped_value` - value from the `wrapped` field (used by Suave)
+ * `wrapped_input` - data from the `wrapped` field (used by Suave)
+ * `wrapped_v` - V field of the signature from the `wrapped` field (used by Suave)
+ * `wrapped_r` - R field of the signature from the `wrapped` field (used by Suave)
+ * `wrapped_s` - S field of the signature from the `wrapped` field (used by Suave)
+ * `wrapped_hash` - hash from the `wrapped` field (used by Suave)
"""
- @type t :: %__MODULE__{
- block: %Ecto.Association.NotLoaded{} | Block.t() | nil,
- block_hash: Hash.t() | nil,
- block_number: Block.block_number() | nil,
- created_contract_address: %Ecto.Association.NotLoaded{} | Address.t() | nil,
- created_contract_address_hash: Hash.Address.t() | nil,
- created_contract_code_indexed_at: DateTime.t() | nil,
- cumulative_gas_used: Gas.t() | nil,
- earliest_processing_start: DateTime.t() | nil,
- error: String.t() | nil,
- forks: %Ecto.Association.NotLoaded{} | [Fork.t()],
- from_address: %Ecto.Association.NotLoaded{} | Address.t(),
- from_address_hash: Hash.Address.t(),
- gas: Gas.t(),
- gas_price: wei_per_gas,
- gas_used: Gas.t() | nil,
- hash: Hash.t(),
- index: transaction_index | nil,
- input: Data.t(),
- internal_transactions: %Ecto.Association.NotLoaded{} | [InternalTransaction.t()],
- logs: %Ecto.Association.NotLoaded{} | [Log.t()],
- nonce: non_neg_integer(),
- r: r(),
- s: s(),
- status: Status.t() | nil,
- to_address: %Ecto.Association.NotLoaded{} | Address.t() | nil,
- to_address_hash: Hash.Address.t() | nil,
- uncles: %Ecto.Association.NotLoaded{} | [Block.t()],
- v: v(),
- value: Wei.t(),
- revert_reason: String.t() | nil,
- max_priority_fee_per_gas: wei_per_gas | nil,
- max_fee_per_gas: wei_per_gas | nil,
- type: non_neg_integer() | nil,
- has_error_in_internal_txs: boolean()
- }
+ @type t ::
+ Map.merge(
+ %__MODULE__{
+ block: %Ecto.Association.NotLoaded{} | Block.t() | nil,
+ block_hash: Hash.t() | nil,
+ block_number: Block.block_number() | nil,
+ block_consensus: boolean(),
+ block_timestamp: DateTime.t() | nil,
+ created_contract_address: %Ecto.Association.NotLoaded{} | Address.t() | nil,
+ created_contract_address_hash: Hash.Address.t() | nil,
+ created_contract_code_indexed_at: DateTime.t() | nil,
+ cumulative_gas_used: Gas.t() | nil,
+ earliest_processing_start: DateTime.t() | nil,
+ error: String.t() | nil,
+ forks: %Ecto.Association.NotLoaded{} | [Fork.t()],
+ from_address: %Ecto.Association.NotLoaded{} | Address.t(),
+ from_address_hash: Hash.Address.t(),
+ gas: Gas.t(),
+ gas_price: wei_per_gas | nil,
+ gas_used: Gas.t() | nil,
+ hash: Hash.t(),
+ index: transaction_index | nil,
+ input: Data.t(),
+ internal_transactions: %Ecto.Association.NotLoaded{} | [InternalTransaction.t()],
+ logs: %Ecto.Association.NotLoaded{} | [Log.t()],
+ nonce: non_neg_integer(),
+ r: r(),
+ s: s(),
+ status: Status.t() | nil,
+ to_address: %Ecto.Association.NotLoaded{} | Address.t() | nil,
+ to_address_hash: Hash.Address.t() | nil,
+ uncles: %Ecto.Association.NotLoaded{} | [Block.t()],
+ v: v(),
+ value: Wei.t(),
+ revert_reason: String.t() | nil,
+ max_priority_fee_per_gas: wei_per_gas | nil,
+ max_fee_per_gas: wei_per_gas | nil,
+ type: non_neg_integer() | nil,
+ has_error_in_internal_txs: boolean(),
+ transaction_fee_log: any(),
+ transaction_fee_token: any()
+ },
+ suave
+ )
+
+ if Application.compile_env(:explorer, :chain_type) == "suave" do
+ @type suave :: %{
+ execution_node: %Ecto.Association.NotLoaded{} | Address.t() | nil,
+ execution_node_hash: Hash.Address.t() | nil,
+ wrapped_type: non_neg_integer() | nil,
+ wrapped_nonce: non_neg_integer() | nil,
+ wrapped_to_address: %Ecto.Association.NotLoaded{} | Address.t() | nil,
+ wrapped_to_address_hash: Hash.Address.t() | nil,
+ wrapped_gas: Gas.t() | nil,
+ wrapped_gas_price: wei_per_gas | nil,
+ wrapped_max_priority_fee_per_gas: wei_per_gas | nil,
+ wrapped_max_fee_per_gas: wei_per_gas | nil,
+ wrapped_value: Wei.t() | nil,
+ wrapped_input: Data.t() | nil,
+ wrapped_v: v() | nil,
+ wrapped_r: r() | nil,
+ wrapped_s: s() | nil,
+ wrapped_hash: Hash.t() | nil
+ }
+ else
+ @type suave :: %{}
+ end
@derive {Poison.Encoder,
only: [
:block_number,
+ :block_timestamp,
:cumulative_gas_used,
:error,
:gas,
@@ -200,6 +258,7 @@ defmodule Explorer.Chain.Transaction do
@derive {Jason.Encoder,
only: [
:block_number,
+ :block_timestamp,
:cumulative_gas_used,
:error,
:gas,
@@ -220,6 +279,8 @@ defmodule Explorer.Chain.Transaction do
@primary_key {:hash, Hash.Full, autogenerate: false}
schema "transactions" do
field(:block_number, :integer)
+ field(:block_consensus, :boolean)
+ field(:block_timestamp, :utc_datetime_usec)
field(:cumulative_gas_used, :decimal)
field(:earliest_processing_start, :utc_datetime_usec)
field(:error, :string)
@@ -240,6 +301,11 @@ defmodule Explorer.Chain.Transaction do
field(:max_fee_per_gas, Wei)
field(:type, :integer)
field(:has_error_in_internal_txs, :boolean)
+ field(:has_token_transfers, :boolean, virtual: true)
+
+ # stability virtual fields
+ field(:transaction_fee_log, :any, virtual: true)
+ field(:transaction_fee_token, :any, virtual: true)
# A transient field for deriving old block hash during transaction upserts.
# Used to force refetch of a block in case a transaction is re-collated
@@ -274,6 +340,11 @@ defmodule Explorer.Chain.Transaction do
has_many(:uncles, through: [:forks, :uncle])
+ has_one(:zkevm_batch_transaction, BatchTransaction, foreign_key: :hash)
+ has_one(:zkevm_batch, through: [:zkevm_batch_transaction, :batch])
+ has_one(:zkevm_sequence_transaction, through: [:zkevm_batch, :sequence_transaction])
+ has_one(:zkevm_verify_transaction, through: [:zkevm_batch, :verify_transaction])
+
belongs_to(
:created_contract_address,
Address,
@@ -281,6 +352,37 @@ defmodule Explorer.Chain.Transaction do
references: :hash,
type: Hash.Address
)
+
+ if System.get_env("CHAIN_TYPE") == "suave" do
+ belongs_to(
+ :execution_node,
+ Address,
+ foreign_key: :execution_node_hash,
+ references: :hash,
+ type: Hash.Address
+ )
+
+ field(:wrapped_type, :integer)
+ field(:wrapped_nonce, :integer)
+ field(:wrapped_gas, :decimal)
+ field(:wrapped_gas_price, Wei)
+ field(:wrapped_max_priority_fee_per_gas, Wei)
+ field(:wrapped_max_fee_per_gas, Wei)
+ field(:wrapped_value, Wei)
+ field(:wrapped_input, Data)
+ field(:wrapped_v, :decimal)
+ field(:wrapped_r, :decimal)
+ field(:wrapped_s, :decimal)
+ field(:wrapped_hash, Hash.Full)
+
+ belongs_to(
+ :wrapped_to_address,
+ Address,
+ foreign_key: :wrapped_to_address_hash,
+ references: :hash,
+ type: Hash.Address
+ )
+ end
end
@doc """
@@ -304,6 +406,25 @@ defmodule Explorer.Chain.Transaction do
iex> changeset.valid?
true
+ A pending transaction does not have a `gas_price` (Erigon)
+
+ iex> changeset = Explorer.Chain.Transaction.changeset(
+ ...> %Transaction{},
+ ...> %{
+ ...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
+ ...> gas: 4700000,
+ ...> hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6",
+ ...> input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029",
+ ...> nonce: 0,
+ ...> r: 0xAD3733DF250C87556335FFE46C23E34DBAFFDE93097EF92F52C88632A40F0C75,
+ ...> s: 0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3,
+ ...> v: 0x8d,
+ ...> value: 0
+ ...> }
+ ...> )
+ iex> changeset.valid?
+ true
+
A collated transaction MUST have an `index` so its position in the `block` is known and the `cumulative_gas_used` ane
`gas_used` to know its fees.
@@ -414,7 +535,10 @@ defmodule Explorer.Chain.Transaction do
"""
def changeset(%__MODULE__{} = transaction, attrs \\ %{}) do
- attrs_to_cast = @required_attrs ++ @optional_attrs
+ attrs_to_cast =
+ @required_attrs ++
+ @optional_attrs ++
+ if Application.get_env(:explorer, :chain_type) == "suave", do: @suave_optional_attrs, else: @empty_attrs
transaction
|> cast(attrs, attrs_to_cast)
@@ -429,6 +553,11 @@ defmodule Explorer.Chain.Transaction do
|> unique_constraint(:hash)
end
+ @spec block_timestamp(t()) :: DateTime.t()
+ def block_timestamp(%{block_number: nil, inserted_at: time}), do: time
+ def block_timestamp(%{block_timestamp: time}) when not is_nil(time), do: time
+ def block_timestamp(%{block: %{timestamp: time}}), do: time
+
def preload_token_transfers(query, address_hash) do
token_transfers_query =
from(
@@ -461,14 +590,17 @@ defmodule Explorer.Chain.Transaction do
{number, ""} ->
binary_revert_reason = :binary.encode_unsigned(number)
- decoded_input_data(
- %Transaction{
- to_address: smart_contract,
- hash: hash,
- input: %Data{bytes: binary_revert_reason}
- },
- options
- )
+ {result, _, _} =
+ decoded_input_data(
+ %Transaction{
+ to_address: smart_contract,
+ hash: hash,
+ input: %Data{bytes: binary_revert_reason}
+ },
+ options
+ )
+
+ result
_ ->
hex_revert_reason
@@ -476,16 +608,33 @@ defmodule Explorer.Chain.Transaction do
end
# Because there is no contract association, we know the contract was not verified
- def decoded_input_data(tx, skip_sig_provider? \\ false, options)
-
- def decoded_input_data(%__MODULE__{to_address: nil}, _, _), do: {:error, :no_to_address}
- def decoded_input_data(%NotLoaded{}, _, _), do: {:error, :not_loaded}
-
- def decoded_input_data(%__MODULE__{input: %{bytes: bytes}}, _, _) when bytes in [nil, <<>>],
- do: {:error, :no_input_data}
+ @spec decoded_input_data(
+ NotLoaded.t() | Transaction.t(),
+ boolean(),
+ [Chain.api?()],
+ full_abi_acc,
+ methods_acc
+ ) ::
+ {error_type | success_type, full_abi_acc, methods_acc}
+ when full_abi_acc: map(),
+ methods_acc: map(),
+ error_type: {:error, any()} | {:error, :contract_not_verified | :contract_verified, list()},
+ success_type: {:ok | binary(), any()} | {:ok, binary(), binary(), list()}
+ def decoded_input_data(tx, skip_sig_provider? \\ false, options, full_abi_acc \\ %{}, methods_acc \\ %{})
+
+ def decoded_input_data(%__MODULE__{to_address: nil}, _, _, full_abi_acc, methods_acc),
+ do: {{:error, :no_to_address}, full_abi_acc, methods_acc}
+
+ def decoded_input_data(%NotLoaded{}, _, _, full_abi_acc, methods_acc),
+ do: {{:error, :not_loaded}, full_abi_acc, methods_acc}
+
+ def decoded_input_data(%__MODULE__{input: %{bytes: bytes}}, _, _, full_abi_acc, methods_acc)
+ when bytes in [nil, <<>>],
+ do: {{:error, :no_input_data}, full_abi_acc, methods_acc}
if not Application.compile_env(:explorer, :decode_not_a_contract_calls) do
- def decoded_input_data(%__MODULE__{to_address: %{contract_code: nil}}, _, _), do: {:error, :not_a_contract_call}
+ def decoded_input_data(%__MODULE__{to_address: %{contract_code: nil}}, _, _, full_abi_acc, methods_acc),
+ do: {{:error, :not_a_contract_call}, full_abi_acc, methods_acc}
end
def decoded_input_data(
@@ -495,7 +644,9 @@ defmodule Explorer.Chain.Transaction do
hash: hash
},
skip_sig_provider?,
- options
+ options,
+ full_abi_acc,
+ methods_acc
) do
decoded_input_data(
%__MODULE__{
@@ -504,7 +655,9 @@ defmodule Explorer.Chain.Transaction do
hash: hash
},
skip_sig_provider?,
- options
+ options,
+ full_abi_acc,
+ methods_acc
)
end
@@ -515,7 +668,9 @@ defmodule Explorer.Chain.Transaction do
hash: hash
},
skip_sig_provider?,
- options
+ options,
+ full_abi_acc,
+ methods_acc
) do
decoded_input_data(
%__MODULE__{
@@ -524,7 +679,9 @@ defmodule Explorer.Chain.Transaction do
hash: hash
},
skip_sig_provider?,
- options
+ options,
+ full_abi_acc,
+ methods_acc
)
end
@@ -535,31 +692,30 @@ defmodule Explorer.Chain.Transaction do
hash: hash
},
skip_sig_provider?,
- options
+ options,
+ full_abi_acc,
+ methods_acc
) do
- candidates_query =
- from(
- contract_method in ContractMethod,
- where: contract_method.identifier == ^method_id,
- limit: 1
- )
+ {methods, methods_acc} =
+ method_id
+ |> check_methods_cache(methods_acc, options)
candidates =
- candidates_query
- |> Chain.select_repo(options).all()
+ methods
|> Enum.flat_map(fn candidate ->
- case do_decoded_input_data(data, %SmartContract{abi: [candidate.abi], address_hash: nil}, hash, options) do
- {:ok, _, _, _} = decoded -> [decoded]
+ case do_decoded_input_data(data, %SmartContract{abi: [candidate.abi], address_hash: nil}, hash, options, %{}) do
+ {{:ok, _, _, _} = decoded, _} -> [decoded]
_ -> []
end
end)
- {:error, :contract_not_verified,
- if(candidates == [], do: decode_function_call_via_sig_provider(input, hash, skip_sig_provider?), else: candidates)}
+ {{:error, :contract_not_verified,
+ if(candidates == [], do: decode_function_call_via_sig_provider(input, hash, skip_sig_provider?), else: candidates)},
+ full_abi_acc, methods_acc}
end
- def decoded_input_data(%__MODULE__{to_address: %{smart_contract: nil}}, _, _) do
- {:error, :contract_not_verified, []}
+ def decoded_input_data(%__MODULE__{to_address: %{smart_contract: nil}}, _, _, full_abi_acc, methods_acc) do
+ {{:error, :contract_not_verified, []}, full_abi_acc, methods_acc}
end
def decoded_input_data(
@@ -569,11 +725,13 @@ defmodule Explorer.Chain.Transaction do
hash: hash
},
skip_sig_provider?,
- options
+ options,
+ full_abi_acc,
+ methods_acc
) do
- case do_decoded_input_data(data, smart_contract, hash, options) do
+ case do_decoded_input_data(data, smart_contract, hash, options, full_abi_acc) do
# In some cases transactions use methods of some unpredictable contracts, so we can try to look up for method in a whole DB
- {:error, :could_not_decode} ->
+ {{:error, :could_not_decode}, full_abi_acc} ->
case decoded_input_data(
%__MODULE__{
to_address: %{smart_contract: nil},
@@ -581,20 +739,22 @@ defmodule Explorer.Chain.Transaction do
hash: hash
},
skip_sig_provider?,
- options
+ options,
+ full_abi_acc,
+ methods_acc
) do
- {:error, :contract_not_verified, []} ->
- decode_function_call_via_sig_provider_wrapper(input, hash, skip_sig_provider?)
+ {{:error, :contract_not_verified, []}, full_abi_acc, methods_acc} ->
+ {decode_function_call_via_sig_provider_wrapper(input, hash, skip_sig_provider?), full_abi_acc, methods_acc}
- {:error, :contract_not_verified, candidates} ->
- {:error, :contract_verified, candidates}
+ {{:error, :contract_not_verified, candidates}, full_abi_acc, methods_acc} ->
+ {{:error, :contract_verified, candidates}, full_abi_acc, methods_acc}
- _ ->
- {:error, :could_not_decode}
+ {_, full_abi_acc, methods_acc} ->
+ {{:error, :could_not_decode}, full_abi_acc, methods_acc}
end
- output ->
- output
+ {output, full_abi_acc} ->
+ {output, full_abi_acc, methods_acc}
end
end
@@ -608,14 +768,16 @@ defmodule Explorer.Chain.Transaction do
end
end
- defp do_decoded_input_data(data, smart_contract, hash, options) do
- full_abi = Chain.combine_proxy_implementation_abi(smart_contract, options)
+ defp do_decoded_input_data(data, smart_contract, hash, options, full_abi_acc) do
+ {full_abi, full_abi_acc} = check_full_abi_cache(smart_contract, full_abi_acc, options)
- with {:ok, {selector, values}} <- find_and_decode(full_abi, data, hash),
- {:ok, mapping} <- selector_mapping(selector, values, hash),
- identifier <- Base.encode16(selector.method_id, case: :lower),
- text <- function_call(selector.function, mapping),
- do: {:ok, identifier, text, mapping}
+ {with(
+ {:ok, {selector, values}} <- find_and_decode(full_abi, data, hash),
+ {:ok, mapping} <- selector_mapping(selector, values, hash),
+ identifier <- Base.encode16(selector.method_id, case: :lower),
+ text <- function_call(selector.function, mapping),
+ do: {:ok, identifier, text, mapping}
+ ), full_abi_acc}
end
defp decode_function_call_via_sig_provider(%{bytes: data} = input, hash, skip_sig_provider?) do
@@ -625,8 +787,8 @@ defmodule Explorer.Chain.Transaction do
true <- is_list(result),
false <- Enum.empty?(result),
abi <- [result |> List.first() |> Map.put("outputs", []) |> Map.put("type", "function")],
- {:ok, _, _, _} = candidate <-
- do_decoded_input_data(data, %SmartContract{abi: abi, address_hash: nil}, hash, []) do
+ {{:ok, _, _, _} = candidate, _} <-
+ do_decoded_input_data(data, %SmartContract{abi: abi, address_hash: nil}, hash, [], %{}) do
[candidate]
else
_ ->
@@ -634,6 +796,30 @@ defmodule Explorer.Chain.Transaction do
end
end
+ defp check_methods_cache(method_id, methods_acc, options) do
+ if Map.has_key?(methods_acc, method_id) do
+ {methods_acc[method_id], methods_acc}
+ else
+ candidates_query = ContractMethod.find_contract_method_query(method_id, 1)
+
+ result =
+ candidates_query
+ |> Chain.select_repo(options).all()
+
+ {result, Map.put(methods_acc, method_id, result)}
+ end
+ end
+
+ defp check_full_abi_cache(%{address_hash: address_hash} = smart_contract, full_abi_acc, options) do
+ if !is_nil(address_hash) && Map.has_key?(full_abi_acc, address_hash) do
+ {full_abi_acc[address_hash], full_abi_acc}
+ else
+ full_abi = Proxy.combine_proxy_implementation_abi(smart_contract, options)
+
+ {full_abi, Map.put(full_abi_acc, address_hash, full_abi)}
+ end
+ end
+
def get_method_name(
%__MODULE__{
input: %{bytes: <>}
@@ -651,10 +837,10 @@ defmodule Explorer.Chain.Transaction do
true,
[]
) do
- {:error, :contract_not_verified, [{:ok, _method_id, decoded_func, _}]} ->
+ {{:error, :contract_not_verified, [{:ok, _method_id, decoded_func, _}]}, _, _} ->
parse_method_name(decoded_func)
- {:error, :contract_not_verified, []} ->
+ {{:error, :contract_not_verified, []}, _, _} ->
"0x" <> Base.encode16(method_id, case: :lower)
_ ->
@@ -686,12 +872,12 @@ defmodule Explorer.Chain.Transaction do
end
defp find_and_decode(abi, data, hash) do
- result =
- abi
- |> ABI.parse_specification()
- |> ABI.find_and_decode(data)
-
- {:ok, result}
+ with {%FunctionSelector{}, _mapping} = result <-
+ abi
+ |> ABI.parse_specification()
+ |> ABI.find_and_decode(data) do
+ {:ok, result}
+ end
rescue
e ->
Logger.warn(fn ->
@@ -847,11 +1033,12 @@ defmodule Explorer.Chain.Transaction do
"""
def transactions_with_token_transfers(address_hash, token_hash) do
query = transactions_with_token_transfers_query(address_hash, token_hash)
+ preloads = DenormalizationHelper.extend_block_preload([:from_address, :to_address, :created_contract_address])
from(
t in subquery(query),
order_by: [desc: t.block_number, desc: t.index],
- preload: [:from_address, :to_address, :created_contract_address, :block]
+ preload: ^preloads
)
end
@@ -868,11 +1055,12 @@ defmodule Explorer.Chain.Transaction do
def transactions_with_token_transfers_direction(direction, address_hash) do
query = transactions_with_token_transfers_query_direction(direction, address_hash)
+ preloads = DenormalizationHelper.extend_block_preload([:from_address, :to_address, :created_contract_address])
from(
t in subquery(query),
order_by: [desc: t.block_number, desc: t.index],
- preload: [:from_address, :to_address, :created_contract_address, :block]
+ preload: ^preloads
)
end
@@ -932,4 +1120,560 @@ defmodule Explorer.Chain.Transaction do
limit: 1
)
end
+
+ @doc """
+ Returns true if the transaction is a Rootstock REMASC transaction.
+ """
+ @spec is_rootstock_remasc_transaction(Explorer.Chain.Transaction.t()) :: boolean
+ def is_rootstock_remasc_transaction(%__MODULE__{to_address_hash: to_address_hash}) do
+ case Hash.Address.cast(Application.get_env(:explorer, __MODULE__)[:rootstock_remasc_address]) do
+ {:ok, address} -> address == to_address_hash
+ _ -> false
+ end
+ end
+
+ @doc """
+ Returns true if the transaction is a Rootstock bridge transaction.
+ """
+ @spec is_rootstock_bridge_transaction(Explorer.Chain.Transaction.t()) :: boolean
+ def is_rootstock_bridge_transaction(%__MODULE__{to_address_hash: to_address_hash}) do
+ case Hash.Address.cast(Application.get_env(:explorer, __MODULE__)[:rootstock_bridge_address]) do
+ {:ok, address} -> address == to_address_hash
+ _ -> false
+ end
+ end
+
+ @api_true [api?: true]
+ @transaction_fee_event_signature "0x99e7b0ba56da2819c37c047f0511fd2bf6c9b4e27b4a979a19d6da0f74be8155"
+ @transaction_fee_event_abi [
+ %{
+ "anonymous" => false,
+ "inputs" => [
+ %{
+ "indexed" => false,
+ "internalType" => "address",
+ "name" => "token",
+ "type" => "address"
+ },
+ %{
+ "indexed" => false,
+ "internalType" => "uint256",
+ "name" => "totalFee",
+ "type" => "uint256"
+ },
+ %{
+ "indexed" => false,
+ "internalType" => "address",
+ "name" => "validator",
+ "type" => "address"
+ },
+ %{
+ "indexed" => false,
+ "internalType" => "uint256",
+ "name" => "validatorFee",
+ "type" => "uint256"
+ },
+ %{
+ "indexed" => false,
+ "internalType" => "address",
+ "name" => "dapp",
+ "type" => "address"
+ },
+ %{
+ "indexed" => false,
+ "internalType" => "uint256",
+ "name" => "dappFee",
+ "type" => "uint256"
+ }
+ ],
+ "name" => "TransactionFee",
+ "type" => "event"
+ }
+ ]
+
+ def maybe_prepare_stability_fees(transactions) do
+ if Application.get_env(:explorer, :chain_type) == "stability" do
+ maybe_prepare_stability_fees_inner(transactions)
+ else
+ transactions
+ end
+ end
+
+ defp maybe_prepare_stability_fees_inner(transactions) when is_list(transactions) do
+ {transactions, _tokens_acc} =
+ Enum.map_reduce(transactions, %{}, fn transaction, tokens_acc ->
+ case Log.fetch_log_by_tx_hash_and_first_topic(transaction.hash, @transaction_fee_event_signature, @api_true) do
+ fee_log when not is_nil(fee_log) ->
+ {:ok, _selector, mapping} = Log.find_and_decode(@transaction_fee_event_abi, fee_log, transaction.hash)
+
+ [{"token", "address", false, token_address_hash}, _, _, _, _, _] = mapping
+
+ {token, new_tokens_acc} = check_tokens_acc(bytes_to_address_hash(token_address_hash), tokens_acc)
+
+ {%Transaction{transaction | transaction_fee_log: mapping, transaction_fee_token: token}, new_tokens_acc}
+
+ _ ->
+ {transaction, tokens_acc}
+ end
+ end)
+
+ transactions
+ end
+
+ defp maybe_prepare_stability_fees_inner(transaction) do
+ [transaction] = maybe_prepare_stability_fees_inner([transaction])
+ transaction
+ end
+
+ defp check_tokens_acc(token_address_hash, tokens_acc) do
+ if Map.has_key?(tokens_acc, token_address_hash) do
+ {tokens_acc[token_address_hash], tokens_acc}
+ else
+ token = Token.get_by_contract_address_hash(token_address_hash, @api_true)
+
+ {token, Map.put(tokens_acc, token_address_hash, token)}
+ end
+ end
+
+ def bytes_to_address_hash(bytes), do: %Hash{byte_count: 20, bytes: bytes}
+
+ @doc """
+ Fetches the transactions related to the address with the given hash, including
+ transactions that only have the address in the `token_transfers` related table
+ and rewards for block validation.
+
+ This query is divided into multiple subqueries intentionally in order to
+ improve the listing performance.
+
+ The `token_transfers` table tends to grow exponentially, and the query results
+ with a `transactions` `join` statement takes too long.
+
+ To solve this the `transaction_hashes` are fetched in a separate query, and
+ paginated through the `block_number` already present in the `token_transfers`
+ table.
+
+ ## Options
+
+ * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
+ `:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the
+ `t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`.
+ * `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and
+ `:key` (a tuple of the lowest/oldest `{block_number, index}`) and. Results will be the transactions older than
+ the `block_number` and `index` that are passed.
+
+ """
+ @spec address_to_transactions_with_rewards(Hash.Address.t(), [
+ Chain.paging_options() | Chain.necessity_by_association_option()
+ ]) :: [__MODULE__.t()]
+ def address_to_transactions_with_rewards(address_hash, options \\ []) when is_list(options) do
+ paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options())
+
+ case Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:has_emission_funds] &&
+ Keyword.get(options, :direction) != :from &&
+ Reward.address_has_rewards?(address_hash) &&
+ Reward.get_validator_payout_key_by_mining_from_db(address_hash, options) do
+ %{payout_key: block_miner_payout_address}
+ when not is_nil(block_miner_payout_address) and address_hash == block_miner_payout_address ->
+ transactions_with_rewards_results(address_hash, options, paging_options)
+
+ _ ->
+ address_to_transactions_without_rewards(address_hash, options)
+ end
+ end
+
+ defp transactions_with_rewards_results(address_hash, options, paging_options) do
+ blocks_range = address_to_transactions_tasks_range_of_blocks(address_hash, options)
+
+ rewards_task =
+ Task.async(fn -> Reward.fetch_emission_rewards_tuples(address_hash, paging_options, blocks_range, options) end)
+
+ [rewards_task | address_to_transactions_tasks(address_hash, options, true)]
+ |> wait_for_address_transactions()
+ |> Enum.sort_by(fn item ->
+ case item do
+ {%Reward{} = emission_reward, _} ->
+ {-emission_reward.block.number, 1}
+
+ item ->
+ process_item(item)
+ end
+ end)
+ |> Enum.dedup_by(fn item ->
+ case item do
+ {%Reward{} = emission_reward, _} ->
+ {emission_reward.block_hash, emission_reward.address_hash, emission_reward.address_type}
+
+ transaction ->
+ transaction.hash
+ end
+ end)
+ |> Enum.take(paging_options.page_size)
+ end
+
+ @doc false
+ def address_to_transactions_tasks_range_of_blocks(address_hash, options) do
+ extremums_list =
+ address_hash
+ |> transactions_block_numbers_at_address(options)
+ |> Enum.map(fn query ->
+ extremum_query =
+ from(
+ q in subquery(query),
+ select: %{min_block_number: min(q.block_number), max_block_number: max(q.block_number)}
+ )
+
+ extremum_query
+ |> Repo.one!()
+ end)
+
+ extremums_list
+ |> Enum.reduce(%{min_block_number: nil, max_block_number: 0}, fn %{
+ min_block_number: min_number,
+ max_block_number: max_number
+ },
+ extremums_result ->
+ current_min_number = Map.get(extremums_result, :min_block_number)
+ current_max_number = Map.get(extremums_result, :max_block_number)
+
+ extremums_result
+ |> process_extremums_result_against_min_number(current_min_number, min_number)
+ |> process_extremums_result_against_max_number(current_max_number, max_number)
+ end)
+ end
+
+ defp transactions_block_numbers_at_address(address_hash, options) do
+ direction = Keyword.get(options, :direction)
+
+ options
+ |> address_to_transactions_tasks_query(true)
+ |> not_pending_transactions()
+ |> select([t], t.block_number)
+ |> matching_address_queries_list(direction, address_hash)
+ end
+
+ defp process_extremums_result_against_min_number(extremums_result, current_min_number, min_number)
+ when is_number(current_min_number) and
+ not (is_number(min_number) and min_number > 0 and min_number < current_min_number) do
+ extremums_result
+ end
+
+ defp process_extremums_result_against_min_number(extremums_result, _current_min_number, min_number) do
+ extremums_result
+ |> Map.put(:min_block_number, min_number)
+ end
+
+ defp process_extremums_result_against_max_number(extremums_result, current_max_number, max_number)
+ when is_number(max_number) and max_number > 0 and max_number > current_max_number do
+ extremums_result
+ |> Map.put(:max_block_number, max_number)
+ end
+
+ defp process_extremums_result_against_max_number(extremums_result, _current_max_number, _max_number) do
+ extremums_result
+ end
+
+ defp process_item(item) do
+ block_number = if item.block_number, do: -item.block_number, else: 0
+ index = if item.index, do: -item.index, else: 0
+ {block_number, index}
+ end
+
+ @spec address_to_transactions_without_rewards(
+ Hash.Address.t(),
+ [
+ Chain.paging_options()
+ | Chain.necessity_by_association_option()
+ | {:sorting, SortingHelper.sorting_params()}
+ ],
+ boolean()
+ ) :: [__MODULE__.t()]
+ def address_to_transactions_without_rewards(address_hash, options, old_ui? \\ true) do
+ paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options())
+
+ address_hash
+ |> address_to_transactions_tasks(options, old_ui?)
+ |> wait_for_address_transactions()
+ |> Enum.sort(compare_custom_sorting(Keyword.get(options, :sorting, [])))
+ |> Enum.dedup_by(& &1.hash)
+ |> Enum.take(paging_options.page_size)
+ end
+
+ defp address_to_transactions_tasks(address_hash, options, old_ui?) do
+ direction = Keyword.get(options, :direction)
+ necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
+ old_ui? = old_ui? || is_tuple(Keyword.get(options, :paging_options, Chain.default_paging_options()).key)
+
+ options
+ |> address_to_transactions_tasks_query(false, old_ui?)
+ |> not_dropped_or_replaced_transactions()
+ |> Chain.join_associations(necessity_by_association)
+ |> put_has_token_transfers_to_tx(old_ui?)
+ |> matching_address_queries_list(direction, address_hash)
+ |> Enum.map(fn query -> Task.async(fn -> Chain.select_repo(options).all(query) end) end)
+ end
+
+ @doc """
+ Returns the address to transactions tasks query based on provided options.
+ Boolean `only_mined?` argument specifies if only mined transactions should be returned,
+ boolean `old_ui?` argument specifies if the query is for the old UI, i.e. is query dynamically sorted or no.
+ """
+ @spec address_to_transactions_tasks_query(keyword, boolean, boolean) :: Ecto.Query.t()
+ def address_to_transactions_tasks_query(options, only_mined? \\ false, old_ui? \\ true)
+
+ def address_to_transactions_tasks_query(options, only_mined?, true) do
+ from_block = Chain.from_block(options)
+ to_block = Chain.to_block(options)
+
+ options
+ |> Keyword.get(:paging_options, Chain.default_paging_options())
+ |> fetch_transactions(from_block, to_block, !only_mined?)
+ end
+
+ def address_to_transactions_tasks_query(options, _only_mined?, false) do
+ from_block = Chain.from_block(options)
+ to_block = Chain.to_block(options)
+ paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options())
+ sorting_options = Keyword.get(options, :sorting, [])
+
+ fetch_transactions_with_custom_sorting(paging_options, from_block, to_block, sorting_options)
+ end
+
+ @doc """
+ Waits for the address transactions tasks to complete and returns the transactions flattened
+ in case of success or raises an error otherwise.
+ """
+ @spec wait_for_address_transactions([Task.t()]) :: [__MODULE__.t()]
+ def wait_for_address_transactions(tasks) do
+ tasks
+ |> Task.yield_many(:timer.seconds(20))
+ |> Enum.flat_map(fn {_task, res} ->
+ case res do
+ {:ok, result} ->
+ result
+
+ {:exit, reason} ->
+ raise "Query fetching address transactions terminated: #{inspect(reason)}"
+
+ nil ->
+ raise "Query fetching address transactions timed out."
+ end
+ end)
+ end
+
+ defp compare_custom_sorting([{order, :value}]) do
+ fn a, b ->
+ case Decimal.compare(Wei.to(a.value, :wei), Wei.to(b.value, :wei)) do
+ :eq -> compare_default_sorting(a, b)
+ :gt -> order == :desc
+ :lt -> order == :asc
+ end
+ end
+ end
+
+ defp compare_custom_sorting([{:dynamic, :fee, order, _dynamic_fee}]) do
+ fn a, b ->
+ nil_case =
+ case order do
+ :desc_nulls_last -> Decimal.new("-inf")
+ :asc_nulls_first -> Decimal.new("inf")
+ end
+
+ a_fee = a |> Chain.fee(:wei) |> elem(1) || nil_case
+ b_fee = b |> Chain.fee(:wei) |> elem(1) || nil_case
+
+ case Decimal.compare(a_fee, b_fee) do
+ :eq -> compare_default_sorting(a, b)
+ :gt -> order == :desc_nulls_last
+ :lt -> order == :asc_nulls_first
+ end
+ end
+ end
+
+ defp compare_custom_sorting([]), do: &compare_default_sorting/2
+
+ defp compare_default_sorting(a, b) do
+ case {
+ compare(a.block_number, b.block_number),
+ compare(a.index, b.index),
+ DateTime.compare(a.inserted_at, b.inserted_at),
+ compare(Hash.to_integer(a.hash), Hash.to_integer(b.hash))
+ } do
+ {:lt, _, _, _} -> false
+ {:eq, :lt, _, _} -> false
+ {:eq, :eq, :lt, _} -> false
+ {:eq, :eq, :eq, :gt} -> false
+ _ -> true
+ end
+ end
+
+ defp compare(a, b) do
+ cond do
+ a < b -> :lt
+ a > b -> :gt
+ true -> :eq
+ end
+ end
+
+ @doc """
+ Creates a query to fetch transactions taking into account paging_options (possibly nil),
+ from_block (may be nil), to_block (may be nil) and boolean `with_pending?` that indicates if pending transactions should be included
+ into the query.
+ """
+ @spec fetch_transactions(PagingOptions.t() | nil, non_neg_integer | nil, non_neg_integer | nil, boolean()) ::
+ Ecto.Query.t()
+ def fetch_transactions(paging_options \\ nil, from_block \\ nil, to_block \\ nil, with_pending? \\ false) do
+ __MODULE__
+ |> order_for_transactions(with_pending?)
+ |> Chain.where_block_number_in_period(from_block, to_block)
+ |> handle_paging_options(paging_options)
+ end
+
+ @default_sorting [
+ desc: :block_number,
+ desc: :index,
+ desc: :inserted_at,
+ asc: :hash
+ ]
+
+ @doc """
+ Creates a query to fetch transactions taking into account paging_options (possibly nil),
+ from_block (may be nil), to_block (may be nil) and sorting_params.
+ """
+ @spec fetch_transactions_with_custom_sorting(
+ PagingOptions.t() | nil,
+ non_neg_integer | nil,
+ non_neg_integer | nil,
+ SortingHelper.sorting_params()
+ ) :: Ecto.Query.t()
+ def fetch_transactions_with_custom_sorting(paging_options, from_block, to_block, sorting) do
+ query = from(transaction in __MODULE__)
+
+ query
+ |> Chain.where_block_number_in_period(from_block, to_block)
+ |> SortingHelper.apply_sorting(sorting, @default_sorting)
+ |> SortingHelper.page_with_sorting(paging_options, sorting, @default_sorting)
+ end
+
+ defp order_for_transactions(query, true) do
+ query
+ |> order_by([transaction],
+ desc: transaction.block_number,
+ desc: transaction.index,
+ desc: transaction.inserted_at,
+ asc: transaction.hash
+ )
+ end
+
+ defp order_for_transactions(query, _) do
+ query
+ |> order_by([transaction], desc: transaction.block_number, desc: transaction.index)
+ end
+
+ @doc """
+ Updates the provided query with necessary `where`s and `limit`s to take into account paging_options (may be nil).
+ """
+ @spec handle_paging_options(Ecto.Query.t() | atom, nil | Explorer.PagingOptions.t()) :: Ecto.Query.t()
+ def handle_paging_options(query, nil), do: query
+
+ def handle_paging_options(query, %PagingOptions{key: nil, page_size: nil}), do: query
+
+ def handle_paging_options(query, paging_options) do
+ query
+ |> page_transaction(paging_options)
+ |> limit(^paging_options.page_size)
+ end
+
+ @doc """
+ Updates the provided query with necessary `where`s to take into account paging_options.
+ """
+ @spec page_transaction(Ecto.Query.t() | atom, Explorer.PagingOptions.t()) :: Ecto.Query.t()
+ def page_transaction(query, %PagingOptions{key: nil}), do: query
+
+ def page_transaction(query, %PagingOptions{is_pending_tx: true} = options),
+ do: page_pending_transaction(query, options)
+
+ def page_transaction(query, %PagingOptions{key: {block_number, index}, is_index_in_asc_order: true}) do
+ where(
+ query,
+ [transaction],
+ transaction.block_number < ^block_number or
+ (transaction.block_number == ^block_number and transaction.index > ^index)
+ )
+ end
+
+ def page_transaction(query, %PagingOptions{key: {block_number, index}}) do
+ where(
+ query,
+ [transaction],
+ transaction.block_number < ^block_number or
+ (transaction.block_number == ^block_number and transaction.index < ^index)
+ )
+ end
+
+ def page_transaction(query, %PagingOptions{key: {index}}) do
+ where(query, [transaction], transaction.index < ^index)
+ end
+
+ @doc """
+ Updates the provided query with necessary `where`s to take into account paging_options.
+ """
+ @spec page_pending_transaction(Ecto.Query.t() | atom, Explorer.PagingOptions.t()) :: Ecto.Query.t()
+ def page_pending_transaction(query, %PagingOptions{key: nil}), do: query
+
+ def page_pending_transaction(query, %PagingOptions{key: {inserted_at, hash}}) do
+ where(
+ query,
+ [transaction],
+ (is_nil(transaction.block_number) and
+ (transaction.inserted_at < ^inserted_at or
+ (transaction.inserted_at == ^inserted_at and transaction.hash > ^hash))) or
+ not is_nil(transaction.block_number)
+ )
+ end
+
+ @doc """
+ Adds a `has_token_transfers` field to the query via `select_merge` if second argument is `false` and returns
+ the query untouched otherwise.
+ """
+ @spec put_has_token_transfers_to_tx(Ecto.Query.t() | atom, boolean) :: Ecto.Query.t()
+ def put_has_token_transfers_to_tx(query, true), do: query
+
+ def put_has_token_transfers_to_tx(query, false) do
+ from(tx in query,
+ select_merge: %{
+ has_token_transfers:
+ fragment(
+ "(SELECT transaction_hash FROM token_transfers WHERE transaction_hash = ? LIMIT 1) IS NOT NULL",
+ tx.hash
+ )
+ }
+ )
+ end
+
+ @doc """
+ Return the dynamic that calculates the fee for transactions.
+ """
+ @spec dynamic_fee :: struct()
+ def dynamic_fee do
+ dynamic([tx], tx.gas_price * fragment("COALESCE(?, ?)", tx.gas_used, tx.gas))
+ end
+
+ @doc """
+ Returns next page params based on the provided transaction.
+ """
+ @spec address_transactions_next_page_params(Explorer.Chain.Transaction.t()) :: %{
+ required(String.t()) => Decimal.t() | Wei.t() | non_neg_integer | DateTime.t() | Hash.t()
+ }
+ def address_transactions_next_page_params(
+ %__MODULE__{block_number: block_number, index: index, inserted_at: inserted_at, hash: hash, value: value} = tx
+ ) do
+ %{
+ "fee" => tx |> Chain.fee(:wei) |> elem(1),
+ "value" => value,
+ "block_number" => block_number,
+ "index" => index,
+ "inserted_at" => inserted_at,
+ "hash" => hash
+ }
+ end
end
diff --git a/apps/explorer/lib/explorer/chain/transaction/history/historian.ex b/apps/explorer/lib/explorer/chain/transaction/history/historian.ex
index d828cb63e794..2c8e4479cf6d 100644
--- a/apps/explorer/lib/explorer/chain/transaction/history/historian.ex
+++ b/apps/explorer/lib/explorer/chain/transaction/history/historian.ex
@@ -6,7 +6,7 @@ defmodule Explorer.Chain.Transaction.History.Historian do
use Explorer.History.Historian
alias Explorer.{Chain, Repo}
- alias Explorer.Chain.{Block, Transaction}
+ alias Explorer.Chain.{Block, DenormalizationHelper, Transaction}
alias Explorer.Chain.Events.Publisher
alias Explorer.Chain.Transaction.History.TransactionStats
alias Explorer.History.Process, as: HistoryProcess
@@ -89,25 +89,38 @@ defmodule Explorer.Chain.Transaction.History.Historian do
Logger.info("tx/per day chart: min/max block numbers [#{min_block}, #{max_block}]")
all_transactions_query =
- from(
- transaction in Transaction,
- where: transaction.block_number >= ^min_block and transaction.block_number <= ^max_block
- )
+ if DenormalizationHelper.denormalization_finished?() do
+ from(
+ transaction in Transaction,
+ where: transaction.block_number >= ^min_block and transaction.block_number <= ^max_block,
+ where: transaction.block_consensus == true,
+ select: transaction
+ )
+ else
+ from(
+ transaction in Transaction,
+ where: transaction.block_number >= ^min_block and transaction.block_number <= ^max_block
+ )
+ end
all_blocks_query =
from(
block in Block,
where: block.consensus == true,
where: block.number >= ^min_block and block.number <= ^max_block,
- select: block.hash
+ select: block.number
)
query =
- from(transaction in subquery(all_transactions_query),
- join: block in subquery(all_blocks_query),
- on: transaction.block_hash == block.hash,
- select: transaction
- )
+ if DenormalizationHelper.denormalization_finished?() do
+ all_transactions_query
+ else
+ from(transaction in subquery(all_transactions_query),
+ join: block in subquery(all_blocks_query),
+ on: transaction.block_number == block.number,
+ select: transaction
+ )
+ end
num_transactions = Repo.aggregate(query, :count, :hash, timeout: :infinity)
Logger.info("tx/per day chart: num of transactions #{num_transactions}")
@@ -115,11 +128,18 @@ defmodule Explorer.Chain.Transaction.History.Historian do
Logger.info("tx/per day chart: total gas used #{gas_used}")
total_fee_query =
- from(transaction in subquery(all_transactions_query),
- join: block in subquery(all_blocks_query),
- on: transaction.block_hash == block.hash,
- select: fragment("SUM(? * ?)", transaction.gas_price, transaction.gas_used)
- )
+ if DenormalizationHelper.denormalization_finished?() do
+ from(transaction in subquery(all_transactions_query),
+ select: fragment("SUM(? * ?)", transaction.gas_price, transaction.gas_used)
+ )
+ else
+ from(transaction in subquery(all_transactions_query),
+ join: block in Block,
+ on: transaction.block_hash == block.hash,
+ where: block.consensus == true,
+ select: fragment("SUM(? * ?)", transaction.gas_price, transaction.gas_used)
+ )
+ end
total_fee = Repo.one(total_fee_query, timeout: :infinity)
Logger.info("tx/per day chart: total fee #{total_fee}")
diff --git a/apps/explorer/lib/explorer/chain/transaction/state_change.ex b/apps/explorer/lib/explorer/chain/transaction/state_change.ex
index b0382e3dd3ac..c0b70f23c493 100644
--- a/apps/explorer/lib/explorer/chain/transaction/state_change.ex
+++ b/apps/explorer/lib/explorer/chain/transaction/state_change.ex
@@ -1,6 +1,274 @@
defmodule Explorer.Chain.Transaction.StateChange do
@moduledoc """
- Struct for storing state changes
+ Helper functions and struct for storing state changes
"""
- defstruct [:coin_or_token_transfers, :address, :balance_before, :balance_after, :balance_diff, :miner?]
+
+ alias Explorer.Chain
+ alias Explorer.Chain.{Hash, TokenTransfer, Wei}
+ alias Explorer.Chain.Transaction.StateChange
+
+ defstruct [:coin_or_token_transfers, :address, :token_id, :balance_before, :balance_after, :balance_diff, :miner?]
+
+ @type t :: %__MODULE__{
+ coin_or_token_transfers: :coin | [TokenTransfer.t()],
+ address: Hash.Address.t(),
+ token_id: nil | non_neg_integer(),
+ balance_before: Wei.t() | Decimal.t(),
+ balance_after: Wei.t() | Decimal.t(),
+ balance_diff: Wei.t() | Decimal.t(),
+ miner?: boolean()
+ }
+
+ def coin_balances_before(tx, block_txs, from_before, to_before, miner_before) do
+ block = tx.block
+
+ block_txs
+ |> Enum.reduce_while(
+ {from_before, to_before, miner_before},
+ fn block_tx, {block_from, block_to, block_miner} = state ->
+ if block_tx.index < tx.index do
+ {:cont,
+ {update_coin_balance_from_tx(tx.from_address_hash, block_tx, block_from, block),
+ update_coin_balance_from_tx(tx.to_address_hash, block_tx, block_to, block),
+ update_coin_balance_from_tx(tx.block.miner_hash, block_tx, block_miner, block)}}
+ else
+ # txs ordered by index ascending, so we can halt after facing index greater or equal than index of our tx
+ {:halt, state}
+ end
+ end
+ )
+ end
+
+ def update_coin_balance_from_tx(address_hash, tx, balance, block) do
+ from = tx.from_address_hash
+ to = tx.to_address_hash
+ miner = block.miner_hash
+
+ balance
+ |> (&if(address_hash == from, do: Wei.sub(&1, from_loss(tx)), else: &1)).()
+ |> (&if(address_hash == to, do: Wei.sum(&1, to_profit(tx)), else: &1)).()
+ |> (&if(address_hash == miner, do: Wei.sum(&1, miner_profit(tx, block)), else: &1)).()
+ end
+
+ def token_balances_before(balances_before, tx, block_txs) do
+ block_txs
+ |> Enum.reduce_while(
+ balances_before,
+ fn block_tx, state ->
+ if block_tx.index < tx.index do
+ {:cont, do_update_token_balances_from_token_transfers(block_tx.token_transfers, state)}
+ else
+ # txs ordered by index ascending, so we can halt after facing index greater or equal than index of our tx
+ {:halt, state}
+ end
+ end
+ )
+ end
+
+ def do_update_token_balances_from_token_transfers(
+ token_transfers,
+ balances_map,
+ include_transfers \\ :no
+ ) do
+ Enum.reduce(
+ token_transfers,
+ balances_map,
+ &token_transfers_balances_reducer(&1, &2, include_transfers)
+ )
+ end
+
+ defp token_transfers_balances_reducer(transfer, state_balances_map, include_transfers) do
+ from = transfer.from_address
+ to = transfer.to_address
+ token = transfer.token_contract_address_hash
+
+ state_balances_map
+ |> case do
+ # from address is needed to be updated in our map
+ %{^from => %{^token => values}} = balances_map ->
+ put_in(
+ balances_map,
+ Enum.map([from, token], &Access.key(&1, %{})),
+ do_update_balance(values, :from, transfer, include_transfers)
+ )
+
+ # we are not interested in this address
+ balances_map ->
+ balances_map
+ end
+ |> case do
+ # to address is needed to be updated in our map
+ %{^to => %{^token => values}} = balances_map ->
+ put_in(
+ balances_map,
+ Enum.map([to, token], &Access.key(&1, %{})),
+ do_update_balance(values, :to, transfer, include_transfers)
+ )
+
+ # we are not interested in this address
+ balances_map ->
+ balances_map
+ end
+ end
+
+ # point of this function is to include all transfers for frontend if option :include_transfer is passed
+ defp do_update_balance(old_val, type, transfer, :include_transfers) do
+ old_val_with_transfer =
+ Map.update(old_val, :transfers, [{type, transfer}], fn transfers -> [{type, transfer} | transfers] end)
+
+ do_update_balance(old_val_with_transfer, type, transfer, :no)
+ end
+
+ defp do_update_balance(old_val, type, transfer, _) do
+ token_ids = if transfer.token.type == "ERC-1155", do: transfer.token_ids, else: [nil]
+ transfer_amounts = transfer.amounts || [transfer.amount || 1]
+
+ sub_or_add =
+ case type do
+ :from -> &Decimal.sub/2
+ :to -> &Decimal.add/2
+ end
+
+ token_ids
+ |> Stream.zip(transfer_amounts)
+ |> Enum.reduce(old_val, fn {id, amount}, ids_to_balances ->
+ case ids_to_balances do
+ %{^id => val} -> %{ids_to_balances | id => sub_or_add.(val, amount)}
+ _ -> ids_to_balances
+ end
+ end)
+ end
+
+ def from_loss(tx) do
+ {_, fee} = Chain.fee(tx, :wei)
+
+ if error?(tx) do
+ %Wei{value: fee}
+ else
+ Wei.sum(tx.value, %Wei{value: fee})
+ end
+ end
+
+ def to_profit(tx) do
+ if error?(tx) do
+ %Wei{value: 0}
+ else
+ tx.value
+ end
+ end
+
+ defp miner_profit(tx, block) do
+ base_fee_per_gas = block.base_fee_per_gas || %Wei{value: Decimal.new(0)}
+ max_priority_fee_per_gas = tx.max_priority_fee_per_gas || tx.gas_price
+ max_fee_per_gas = tx.max_fee_per_gas || tx.gas_price
+
+ priority_fee_per_gas =
+ Enum.min_by([max_priority_fee_per_gas, Wei.sub(max_fee_per_gas, base_fee_per_gas)], fn x ->
+ Wei.to(x, :wei)
+ end)
+
+ Wei.mult(priority_fee_per_gas, tx.gas_used)
+ end
+
+ defp error?(tx) do
+ case Chain.transaction_to_status(tx) do
+ {:error, _} -> true
+ _ -> false
+ end
+ end
+
+ def has_diff?(%Wei{value: val}) do
+ not Decimal.eq?(val, Decimal.new(0))
+ end
+
+ def has_diff?(val) do
+ not Decimal.eq?(val, Decimal.new(0))
+ end
+
+ def state_change(address, balance_before, balance_after, miner? \\ true) do
+ %StateChange{
+ coin_or_token_transfers: :coin,
+ address: address,
+ balance_before: balance_before,
+ balance_after: balance_after,
+ balance_diff: balance_after |> Wei.sub(balance_before),
+ miner?: miner?
+ }
+ end
+
+ def native_coin_entries(transaction, from_before_tx, to_before_tx, miner_before_tx) do
+ block = transaction.block
+
+ from_hash = transaction.from_address_hash
+ to_hash = transaction.to_address_hash
+ miner_hash = block.miner_hash
+
+ from_coin_entry =
+ if from_hash not in [to_hash, miner_hash] do
+ from = transaction.from_address
+ from_after_tx = update_coin_balance_from_tx(from_hash, transaction, from_before_tx, block)
+ coin_entry(from, from_before_tx, from_after_tx)
+ end
+
+ to_coin_entry =
+ if not is_nil(to_hash) and to_hash != miner_hash do
+ to = transaction.to_address
+ to_after = update_coin_balance_from_tx(to_hash, transaction, to_before_tx, block)
+ coin_entry(to, to_before_tx, to_after)
+ end
+
+ miner = block.miner
+ miner_after = update_coin_balance_from_tx(miner_hash, transaction, miner_before_tx, block)
+ miner_entry = coin_entry(miner, miner_before_tx, miner_after, true)
+
+ [from_coin_entry, to_coin_entry, miner_entry] |> Enum.reject(&is_nil/1)
+ end
+
+ defp coin_entry(address, balance_before, balance_after, miner? \\ false) do
+ diff = Wei.sub(balance_after, balance_before)
+
+ if has_diff?(diff) do
+ %StateChange{
+ coin_or_token_transfers: :coin,
+ address: address,
+ token_id: nil,
+ balance_before: balance_before,
+ balance_after: balance_after,
+ balance_diff: diff,
+ miner?: miner?
+ }
+ end
+ end
+
+ def token_entries(token_transfers, token_balances_before) do
+ token_balances_after =
+ do_update_token_balances_from_token_transfers(
+ token_transfers,
+ token_balances_before,
+ :include_transfers
+ )
+
+ for {address, token_balances} <- token_balances_after,
+ {token_hash, id_balances_with_transfers} <- token_balances,
+ {%{transfers: transfers}, id_balances} = Map.split(id_balances_with_transfers, [:transfers]),
+ {id, balance} <- id_balances do
+ balance_before = token_balances_before[address][token_hash][id]
+ balance_diff = Decimal.sub(balance, balance_before)
+ transfer = elem(List.first(transfers), 1)
+
+ if transfer.token.type != "ERC-20" or has_diff?(balance_diff) do
+ %StateChange{
+ coin_or_token_transfers: transfers,
+ address: address,
+ token_id: id,
+ balance_before: balance_before,
+ balance_after: balance,
+ balance_diff: balance_diff,
+ miner?: false
+ }
+ end
+ end
+ |> Enum.reject(&is_nil/1)
+ |> Enum.sort_by(fn state_change -> to_string(state_change.address && state_change.address.hash) end)
+ end
end
diff --git a/apps/explorer/lib/explorer/chain/wei.ex b/apps/explorer/lib/explorer/chain/wei.ex
index 533174ba4524..c542c9081a13 100644
--- a/apps/explorer/lib/explorer/chain/wei.ex
+++ b/apps/explorer/lib/explorer/chain/wei.ex
@@ -138,13 +138,24 @@ defmodule Explorer.Chain.Wei do
iex> Explorer.Chain.Wei.sum(first, second)
%Explorer.Chain.Wei{value: Decimal.new(1_123)}
"""
- @spec sum(Wei.t(), Wei.t()) :: Wei.t()
+ @spec sum(Wei.t() | nil, Wei.t() | nil) :: Wei.t() | nil
+ def sum(%Wei{value: wei_1}, %Wei{value: nil}) do
+ wei_1
+ |> from(:wei)
+ end
+
+ def sum(%Wei{value: nil}, %Wei{value: wei_2}) do
+ wei_2
+ |> from(:wei)
+ end
+
def sum(%Wei{value: wei_1}, %Wei{value: wei_2}) do
wei_1
|> Decimal.add(wei_2)
|> from(:wei)
end
+ @spec sub(Wei.t(), Wei.t()) :: Wei.t() | nil
@doc """
Subtracts two Wei values.
@@ -155,6 +166,8 @@ defmodule Explorer.Chain.Wei do
iex> Explorer.Chain.Wei.sub(first, second)
%Explorer.Chain.Wei{value: Decimal.new(123)}
"""
+ def sub(_, nil), do: nil
+
def sub(%Wei{value: wei_1}, %Wei{value: wei_2}) do
wei_1
|> Decimal.sub(wei_2)
@@ -206,17 +219,23 @@ defmodule Explorer.Chain.Wei do
"""
- @spec from(ether(), :ether) :: t()
+ @spec from(ether() | nil, :ether) :: t() | nil
+ def from(nil, :ether), do: nil
+
def from(%Decimal{} = ether, :ether) do
%__MODULE__{value: Decimal.mult(ether, @wei_per_ether)}
end
- @spec from(gwei(), :gwei) :: t()
+ @spec from(gwei(), :gwei) :: t() | nil
+ def from(nil, :gwei), do: nil
+
def from(%Decimal{} = gwei, :gwei) do
%__MODULE__{value: Decimal.mult(gwei, @wei_per_gwei)}
end
@spec from(wei(), :wei) :: t()
+ def from(nil, :wei), do: nil
+
def from(%Decimal{} = wei, :wei) do
%__MODULE__{value: wei}
end
@@ -247,17 +266,22 @@ defmodule Explorer.Chain.Wei do
"""
- @spec to(t(), :ether) :: ether()
+ @spec to(t(), :ether) :: ether() | nil
+ def to(nil, :ether), do: nil
+
def to(%__MODULE__{value: wei}, :ether) do
Decimal.div(wei, @wei_per_ether)
end
- @spec to(t(), :gwei) :: gwei()
+ @spec to(t(), :gwei) :: gwei() | nil
+ def to(nil, :gwei), do: nil
+
def to(%__MODULE__{value: wei}, :gwei) do
Decimal.div(wei, @wei_per_gwei)
end
- @spec to(t(), :wei) :: wei()
+ @spec to(t(), :wei) :: wei() | nil
+ def to(nil, :wei), do: nil
def to(%__MODULE__{value: wei}, :wei), do: wei
end
diff --git a/apps/explorer/lib/explorer/chain/zkevm/batch_transaction.ex b/apps/explorer/lib/explorer/chain/zkevm/batch_transaction.ex
new file mode 100644
index 000000000000..74f5fb9b9f86
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/zkevm/batch_transaction.ex
@@ -0,0 +1,37 @@
+defmodule Explorer.Chain.Zkevm.BatchTransaction do
+ @moduledoc "Models a list of transactions related to a batch for zkEVM."
+
+ use Explorer.Schema
+
+ alias Explorer.Chain.{Hash, Transaction}
+ alias Explorer.Chain.Zkevm.TransactionBatch
+
+ @required_attrs ~w(batch_number hash)a
+
+ @type t :: %__MODULE__{
+ batch_number: non_neg_integer(),
+ batch: %Ecto.Association.NotLoaded{} | TransactionBatch.t() | nil,
+ hash: Hash.t(),
+ l2_transaction: %Ecto.Association.NotLoaded{} | Transaction.t() | nil
+ }
+
+ @primary_key false
+ schema "zkevm_batch_l2_transactions" do
+ belongs_to(:batch, TransactionBatch, foreign_key: :batch_number, references: :number, type: :integer)
+ belongs_to(:l2_transaction, Transaction, foreign_key: :hash, primary_key: true, references: :hash, type: Hash.Full)
+
+ timestamps()
+ end
+
+ @doc """
+ Validates that the `attrs` are valid.
+ """
+ @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t()
+ def changeset(%__MODULE__{} = transactions, attrs \\ %{}) do
+ transactions
+ |> cast(attrs, @required_attrs)
+ |> validate_required(@required_attrs)
+ |> foreign_key_constraint(:batch_number)
+ |> unique_constraint(:hash)
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/zkevm/lifecycle_transaction.ex b/apps/explorer/lib/explorer/chain/zkevm/lifecycle_transaction.ex
new file mode 100644
index 000000000000..480b0c0c80fa
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/zkevm/lifecycle_transaction.ex
@@ -0,0 +1,37 @@
+defmodule Explorer.Chain.Zkevm.LifecycleTransaction do
+ @moduledoc "Models an L1 lifecycle transaction for zkEVM."
+
+ use Explorer.Schema
+
+ alias Explorer.Chain.Hash
+ alias Explorer.Chain.Zkevm.TransactionBatch
+
+ @required_attrs ~w(id hash is_verify)a
+
+ @type t :: %__MODULE__{
+ hash: Hash.t(),
+ is_verify: boolean()
+ }
+
+ @primary_key {:id, :integer, autogenerate: false}
+ schema "zkevm_lifecycle_l1_transactions" do
+ field(:hash, Hash.Full)
+ field(:is_verify, :boolean)
+
+ has_many(:sequenced_batches, TransactionBatch, foreign_key: :sequence_id)
+ has_many(:verified_batches, TransactionBatch, foreign_key: :verify_id)
+
+ timestamps()
+ end
+
+ @doc """
+ Validates that the `attrs` are valid.
+ """
+ @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t()
+ def changeset(%__MODULE__{} = txn, attrs \\ %{}) do
+ txn
+ |> cast(attrs, @required_attrs)
+ |> validate_required(@required_attrs)
+ |> unique_constraint(:id)
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/zkevm/reader.ex b/apps/explorer/lib/explorer/chain/zkevm/reader.ex
new file mode 100644
index 000000000000..49f69eaa0d46
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/zkevm/reader.ex
@@ -0,0 +1,149 @@
+defmodule Explorer.Chain.Zkevm.Reader do
+ @moduledoc "Contains read functions for zkevm modules."
+
+ import Ecto.Query,
+ only: [
+ from: 2,
+ limit: 2,
+ order_by: 2,
+ where: 2,
+ where: 3
+ ]
+
+ import Explorer.Chain, only: [select_repo: 1]
+
+ alias Explorer.Chain.Zkevm.{BatchTransaction, LifecycleTransaction, TransactionBatch}
+ alias Explorer.{Chain, PagingOptions, Repo}
+
+ @doc """
+ Reads a batch by its number from database.
+ If the number is :latest, gets the latest batch from `zkevm_transaction_batches` table.
+ Returns {:error, :not_found} in case the batch is not found.
+ """
+ @spec batch(non_neg_integer() | :latest, list()) :: {:ok, map()} | {:error, :not_found}
+ def batch(number, options \\ [])
+
+ def batch(:latest, options) when is_list(options) do
+ TransactionBatch
+ |> order_by(desc: :number)
+ |> limit(1)
+ |> select_repo(options).one()
+ |> case do
+ nil -> {:error, :not_found}
+ batch -> {:ok, batch}
+ end
+ end
+
+ def batch(number, options) when is_list(options) do
+ necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
+
+ TransactionBatch
+ |> where(number: ^number)
+ |> Chain.join_associations(necessity_by_association)
+ |> select_repo(options).one()
+ |> case do
+ nil -> {:error, :not_found}
+ batch -> {:ok, batch}
+ end
+ end
+
+ @doc """
+ Reads a list of batches from `zkevm_transaction_batches` table.
+ """
+ @spec batches(list()) :: list()
+ def batches(options \\ []) do
+ necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
+
+ base_query =
+ from(tb in TransactionBatch,
+ order_by: [desc: tb.number]
+ )
+
+ query =
+ if Keyword.get(options, :confirmed?, false) do
+ base_query
+ |> Chain.join_associations(necessity_by_association)
+ |> where([tb], not is_nil(tb.sequence_id) and tb.sequence_id > 0)
+ |> limit(10)
+ else
+ paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options())
+
+ base_query
+ |> Chain.join_associations(necessity_by_association)
+ |> page_batches(paging_options)
+ |> limit(^paging_options.page_size)
+ end
+
+ select_repo(options).all(query)
+ end
+
+ @doc """
+ Reads a list of L2 transaction hashes from `zkevm_batch_l2_transactions` table.
+ """
+ @spec batch_transactions(non_neg_integer(), list()) :: list()
+ def batch_transactions(batch_number, options \\ []) do
+ query = from(bts in BatchTransaction, where: bts.batch_number == ^batch_number)
+
+ select_repo(options).all(query)
+ end
+
+ @doc """
+ Gets the number of the latest batch with defined verify_id from `zkevm_transaction_batches` table.
+ Returns 0 if not found.
+ """
+ @spec last_verified_batch_number() :: non_neg_integer()
+ def last_verified_batch_number do
+ query =
+ from(tb in TransactionBatch,
+ select: tb.number,
+ where: not is_nil(tb.verify_id),
+ order_by: [desc: tb.number],
+ limit: 1
+ )
+
+ query
+ |> Repo.one()
+ |> Kernel.||(0)
+ end
+
+ @doc """
+ Reads a list of L1 transactions by their hashes from `zkevm_lifecycle_l1_transactions` table.
+ """
+ @spec lifecycle_transactions(list()) :: list()
+ def lifecycle_transactions(l1_tx_hashes) do
+ query =
+ from(
+ lt in LifecycleTransaction,
+ select: {lt.hash, lt.id},
+ where: lt.hash in ^l1_tx_hashes
+ )
+
+ Repo.all(query, timeout: :infinity)
+ end
+
+ @doc """
+ Determines ID of the future lifecycle transaction by reading `zkevm_lifecycle_l1_transactions` table.
+ """
+ @spec next_id() :: non_neg_integer()
+ def next_id do
+ query =
+ from(lt in LifecycleTransaction,
+ select: lt.id,
+ order_by: [desc: lt.id],
+ limit: 1
+ )
+
+ last_id =
+ query
+ |> Repo.one()
+ |> Kernel.||(0)
+
+ last_id + 1
+ end
+
+ defp page_batches(query, %PagingOptions{key: nil}), do: query
+
+ defp page_batches(query, %PagingOptions{key: {number}}) do
+ from(tb in query, where: tb.number < ^number)
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/zkevm/transaction_batch.ex b/apps/explorer/lib/explorer/chain/zkevm/transaction_batch.ex
new file mode 100644
index 000000000000..eda97e1d403c
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/zkevm/transaction_batch.ex
@@ -0,0 +1,60 @@
+defmodule Explorer.Chain.Zkevm.TransactionBatch do
+ @moduledoc "Models a batch of transactions for zkEVM."
+
+ use Explorer.Schema
+
+ alias Explorer.Chain.Hash
+ alias Explorer.Chain.Zkevm.{BatchTransaction, LifecycleTransaction}
+
+ @optional_attrs ~w(sequence_id verify_id)a
+
+ @required_attrs ~w(number timestamp l2_transactions_count global_exit_root acc_input_hash state_root)a
+
+ @type t :: %__MODULE__{
+ number: non_neg_integer(),
+ timestamp: DateTime.t(),
+ l2_transactions_count: non_neg_integer(),
+ global_exit_root: Hash.t(),
+ acc_input_hash: Hash.t(),
+ state_root: Hash.t(),
+ sequence_id: non_neg_integer() | nil,
+ sequence_transaction: %Ecto.Association.NotLoaded{} | LifecycleTransaction.t() | nil,
+ verify_id: non_neg_integer() | nil,
+ verify_transaction: %Ecto.Association.NotLoaded{} | LifecycleTransaction.t() | nil,
+ l2_transactions: %Ecto.Association.NotLoaded{} | [BatchTransaction.t()]
+ }
+
+ @primary_key {:number, :integer, autogenerate: false}
+ schema "zkevm_transaction_batches" do
+ field(:timestamp, :utc_datetime_usec)
+ field(:l2_transactions_count, :integer)
+ field(:global_exit_root, Hash.Full)
+ field(:acc_input_hash, Hash.Full)
+ field(:state_root, Hash.Full)
+
+ belongs_to(:sequence_transaction, LifecycleTransaction,
+ foreign_key: :sequence_id,
+ references: :id,
+ type: :integer
+ )
+
+ belongs_to(:verify_transaction, LifecycleTransaction, foreign_key: :verify_id, references: :id, type: :integer)
+
+ has_many(:l2_transactions, BatchTransaction, foreign_key: :batch_number)
+
+ timestamps()
+ end
+
+ @doc """
+ Validates that the `attrs` are valid.
+ """
+ @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t()
+ def changeset(%__MODULE__{} = batches, attrs \\ %{}) do
+ batches
+ |> cast(attrs, @required_attrs ++ @optional_attrs)
+ |> validate_required(@required_attrs)
+ |> foreign_key_constraint(:sequence_id)
+ |> foreign_key_constraint(:verify_id)
+ |> unique_constraint(:number)
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain_spec/genesis_data.ex b/apps/explorer/lib/explorer/chain_spec/genesis_data.ex
index 89d1db9a044f..cb8a2279b1de 100644
--- a/apps/explorer/lib/explorer/chain_spec/genesis_data.ex
+++ b/apps/explorer/lib/explorer/chain_spec/genesis_data.ex
@@ -66,10 +66,6 @@ defmodule Explorer.ChainSpec.GenesisData do
case fetch_spec(path) do
{:ok, chain_spec} ->
case variant do
- EthereumJSONRPC.Nethermind ->
- Importer.import_emission_rewards(chain_spec)
- {:ok, _} = Importer.import_genesis_accounts(chain_spec)
-
EthereumJSONRPC.Geth ->
{:ok, _} = GethImporter.import_genesis_accounts(chain_spec)
diff --git a/apps/explorer/lib/explorer/chain_spec/geth/importer.ex b/apps/explorer/lib/explorer/chain_spec/geth/importer.ex
index be463750644e..9c8083f6fc77 100644
--- a/apps/explorer/lib/explorer/chain_spec/geth/importer.ex
+++ b/apps/explorer/lib/explorer/chain_spec/geth/importer.ex
@@ -50,6 +50,10 @@ defmodule Explorer.ChainSpec.Geth.Importer do
Chain.import(params)
end
+ def genesis_accounts(%{"genesis" => genesis}) do
+ genesis_accounts(genesis)
+ end
+
def genesis_accounts(chain_spec) do
accounts = chain_spec["alloc"]
@@ -85,6 +89,8 @@ defmodule Explorer.ChainSpec.Geth.Importer do
number
end
+ defp parse_number(""), do: 0
+
defp parse_number(string_number) do
{number, ""} = Integer.parse(string_number, 10)
diff --git a/apps/explorer/lib/explorer/counters/address_gas_usage_counter.ex b/apps/explorer/lib/explorer/counters/address_gas_usage_counter.ex
index 5ca1520cb2b5..573ab8a988a5 100644
--- a/apps/explorer/lib/explorer/counters/address_gas_usage_counter.ex
+++ b/apps/explorer/lib/explorer/counters/address_gas_usage_counter.ex
@@ -5,8 +5,9 @@ defmodule Explorer.Counters.AddressTransactionsGasUsageCounter do
use GenServer
alias Ecto.Changeset
- alias Explorer.{Chain, Repo}
+ alias Explorer.Chain.Address.Counters
alias Explorer.Counters.Helper
+ alias Explorer.Repo
@cache_name :address_transactions_gas_usage_counter
@last_update_key "last_update"
@@ -67,7 +68,7 @@ defmodule Explorer.Counters.AddressTransactionsGasUsageCounter do
defp update_cache(address) do
address_hash_string = to_string(address.hash)
put_into_cache("hash_#{address_hash_string}_#{@last_update_key}", Helper.current_time())
- new_data = Chain.address_to_gas_usage_count(address)
+ new_data = Counters.address_to_gas_usage_count(address)
put_into_cache("hash_#{address_hash_string}", new_data)
put_into_db(address, new_data)
end
diff --git a/apps/explorer/lib/explorer/counters/address_token_transfers_counter.ex b/apps/explorer/lib/explorer/counters/address_token_transfers_counter.ex
index ffec588f3131..db3c82da3b48 100644
--- a/apps/explorer/lib/explorer/counters/address_token_transfers_counter.ex
+++ b/apps/explorer/lib/explorer/counters/address_token_transfers_counter.ex
@@ -5,8 +5,9 @@ defmodule Explorer.Counters.AddressTokenTransfersCounter do
use GenServer
alias Ecto.Changeset
- alias Explorer.{Chain, Repo}
+ alias Explorer.Chain.Address.Counters
alias Explorer.Counters.Helper
+ alias Explorer.Repo
@cache_name :address_token_transfers_counter
@last_update_key "last_update"
@@ -67,7 +68,7 @@ defmodule Explorer.Counters.AddressTokenTransfersCounter do
defp update_cache(address) do
address_hash_string = to_string(address.hash)
put_into_cache("hash_#{address_hash_string}_#{@last_update_key}", Helper.current_time())
- new_data = Chain.address_to_token_transfer_count(address)
+ new_data = Counters.address_to_token_transfer_count(address)
put_into_cache("hash_#{address_hash_string}", new_data)
put_into_db(address, new_data)
end
diff --git a/apps/explorer/lib/explorer/counters/address_tokens_usd_sum.ex b/apps/explorer/lib/explorer/counters/address_tokens_usd_sum.ex
index c2dbd011526a..a18ea263607e 100644
--- a/apps/explorer/lib/explorer/counters/address_tokens_usd_sum.ex
+++ b/apps/explorer/lib/explorer/counters/address_tokens_usd_sum.ex
@@ -53,9 +53,9 @@ defmodule Explorer.Counters.AddressTokenUsdSum do
@spec address_tokens_fiat_sum([{Address.CurrentTokenBalance, Explorer.Chain.Token}]) :: Decimal.t()
defp address_tokens_fiat_sum(token_balances) do
token_balances
- |> Enum.reduce(Decimal.new(0), fn {token_balance, token}, acc ->
- if token_balance.value && token.fiat_value && token.decimals do
- Decimal.add(acc, Chain.balance_in_fiat(token_balance, token))
+ |> Enum.reduce(Decimal.new(0), fn token_balance, acc ->
+ if token_balance.value && token_balance.token.fiat_value && token_balance.token.decimals do
+ Decimal.add(acc, Chain.balance_in_fiat(token_balance))
else
acc
end
diff --git a/apps/explorer/lib/explorer/counters/address_transactions_counter.ex b/apps/explorer/lib/explorer/counters/address_transactions_counter.ex
index e3c6dfffb690..7b2c912335fa 100644
--- a/apps/explorer/lib/explorer/counters/address_transactions_counter.ex
+++ b/apps/explorer/lib/explorer/counters/address_transactions_counter.ex
@@ -5,8 +5,9 @@ defmodule Explorer.Counters.AddressTransactionsCounter do
use GenServer
alias Ecto.Changeset
- alias Explorer.{Chain, Repo}
+ alias Explorer.Chain.Address.Counters
alias Explorer.Counters.Helper
+ alias Explorer.Repo
@cache_name :address_transactions_counter
@last_update_key "last_update"
@@ -67,7 +68,7 @@ defmodule Explorer.Counters.AddressTransactionsCounter do
defp update_cache(address) do
address_hash_string = to_string(address.hash)
put_into_cache("hash_#{address_hash_string}_#{@last_update_key}", Helper.current_time())
- new_data = Chain.address_to_transaction_count(address)
+ new_data = Counters.address_to_transaction_count(address)
put_into_cache("hash_#{address_hash_string}", new_data)
put_into_db(address, new_data)
end
diff --git a/apps/explorer/lib/explorer/counters/addresses_counter.ex b/apps/explorer/lib/explorer/counters/addresses_counter.ex
index 820d96a9152f..51fb845bb945 100644
--- a/apps/explorer/lib/explorer/counters/addresses_counter.ex
+++ b/apps/explorer/lib/explorer/counters/addresses_counter.ex
@@ -7,7 +7,7 @@ defmodule Explorer.Counters.AddressesCounter do
use GenServer
- alias Explorer.Chain
+ alias Explorer.Chain.Address.Counters
@table :addresses_counter
@@ -104,7 +104,7 @@ defmodule Explorer.Counters.AddressesCounter do
Consolidates the info by populating the `:ets` table with the current database information.
"""
def consolidate do
- counter = Chain.count_addresses()
+ counter = Counters.count_addresses()
insert_counter({cache_key(), counter})
end
diff --git a/apps/explorer/lib/explorer/counters/addresses_with_balance_counter.ex b/apps/explorer/lib/explorer/counters/addresses_with_balance_counter.ex
index 1358e52a21e8..53621029afd7 100644
--- a/apps/explorer/lib/explorer/counters/addresses_with_balance_counter.ex
+++ b/apps/explorer/lib/explorer/counters/addresses_with_balance_counter.ex
@@ -7,7 +7,7 @@ defmodule Explorer.Counters.AddressesWithBalanceCounter do
use GenServer
- alias Explorer.Chain
+ alias Explorer.Chain.Address.Counters
@table :addresses_with_balance_counter
@@ -104,7 +104,7 @@ defmodule Explorer.Counters.AddressesWithBalanceCounter do
Consolidates the info by populating the `:ets` table with the current database information.
"""
def consolidate do
- counter = Chain.count_addresses_with_balance()
+ counter = Counters.count_addresses_with_balance()
insert_counter({cache_key(), counter})
end
diff --git a/apps/explorer/lib/explorer/counters/average_block_time.ex b/apps/explorer/lib/explorer/counters/average_block_time.ex
index 9075415854a0..9c7338dc9e97 100644
--- a/apps/explorer/lib/explorer/counters/average_block_time.ex
+++ b/apps/explorer/lib/explorer/counters/average_block_time.ex
@@ -84,7 +84,7 @@ defmodule Explorer.Counters.AverageBlockTime do
timestamps =
timestamps_row
- |> Enum.sort_by(fn {_, timestamp} -> timestamp end, &>=/2)
+ |> Enum.sort_by(fn {_, timestamp} -> timestamp end, &Timex.after?/2)
|> Enum.map(fn {number, timestamp} ->
{number, DateTime.to_unix(timestamp, :millisecond)}
end)
@@ -125,7 +125,7 @@ defmodule Explorer.Counters.AverageBlockTime do
defp compose_durations(durations, block_number, last_block_number, last_timestamp, timestamp) do
block_numbers_range = last_block_number - block_number
- if block_numbers_range == 0 do
+ if block_numbers_range <= 0 do
{durations, block_number, timestamp}
else
duration = (last_timestamp - timestamp) / block_numbers_range
diff --git a/apps/explorer/lib/explorer/counters/block_burned_fee_counter.ex b/apps/explorer/lib/explorer/counters/block_burnt_fee_counter.ex
similarity index 89%
rename from apps/explorer/lib/explorer/counters/block_burned_fee_counter.ex
rename to apps/explorer/lib/explorer/counters/block_burnt_fee_counter.ex
index 85e9359f4294..9c87e313ffe7 100644
--- a/apps/explorer/lib/explorer/counters/block_burned_fee_counter.ex
+++ b/apps/explorer/lib/explorer/counters/block_burnt_fee_counter.ex
@@ -1,15 +1,15 @@
-defmodule Explorer.Counters.BlockBurnedFeeCounter do
+defmodule Explorer.Counters.BlockBurntFeeCounter do
@moduledoc """
- Caches Block Burned Fee counter.
+ Caches Block Burnt Fee counter.
"""
use GenServer
alias Explorer.Chain
alias Explorer.Counters.Helper
- @cache_name :block_burned_fee_counter
+ @cache_name :block_burnt_fee_counter
- config = Application.compile_env(:explorer, Explorer.Counters.BlockBurnedFeeCounter)
+ config = Application.compile_env(:explorer, __MODULE__)
@enable_consolidation Keyword.get(config, :enable_consolidation)
@spec start_link(term()) :: GenServer.on_start()
diff --git a/apps/explorer/lib/explorer/counters/block_priority_fee_counter.ex b/apps/explorer/lib/explorer/counters/block_priority_fee_counter.ex
index f51deb4e2e7a..152679fe830d 100644
--- a/apps/explorer/lib/explorer/counters/block_priority_fee_counter.ex
+++ b/apps/explorer/lib/explorer/counters/block_priority_fee_counter.ex
@@ -1,6 +1,6 @@
defmodule Explorer.Counters.BlockPriorityFeeCounter do
@moduledoc """
- Caches Block Burned Fee counter.
+ Caches Block Priority Fee counter.
"""
use GenServer
diff --git a/apps/explorer/lib/explorer/counters/helper.ex b/apps/explorer/lib/explorer/counters/helper.ex
index 4b202f42426f..fbcccf037adf 100644
--- a/apps/explorer/lib/explorer/counters/helper.ex
+++ b/apps/explorer/lib/explorer/counters/helper.ex
@@ -16,13 +16,13 @@ defmodule Explorer.Counters.Helper do
DateTime.to_unix(utc_now, :millisecond)
end
- def fetch_from_cache(key, cache_name) do
+ def fetch_from_cache(key, cache_name, default \\ 0) do
case :ets.lookup(cache_name, key) do
[{_, value}] ->
value
[] ->
- 0
+ default
end
end
diff --git a/apps/explorer/lib/explorer/eth_rpc.ex b/apps/explorer/lib/explorer/eth_rpc.ex
index ed7e6830802b..d7260cf3987c 100644
--- a/apps/explorer/lib/explorer/eth_rpc.ex
+++ b/apps/explorer/lib/explorer/eth_rpc.ex
@@ -54,13 +54,13 @@ defmodule Explorer.EthRPC do
action: :eth_get_logs,
notes: """
Will never return more than 1000 log entries.\n
- For this reason, you can use pagination options to request the next page. Pagination options params: {"logIndex": "3D", "blockNumber": "6423AC", "transactionIndex": 53} which include parameters from the last log received from the previous request. These three parameters are required for pagination.
+ For this reason, you can use pagination options to request the next page. Pagination options params: {"logIndex": "3D", "blockNumber": "6423AC"} which include parameters from the last log received from the previous request. These three parameters are required for pagination.
""",
example: """
{"id": 0, "jsonrpc": "2.0", "method": "eth_getLogs",
"params": [
{"address": "0xc78Be425090Dbd437532594D12267C5934Cc6c6f",
- "paging_options": {"logIndex": "3D", "blockNumber": "6423AC", "transactionIndex": 53},
+ "paging_options": {"logIndex": "3D", "blockNumber": "6423AC"},
"fromBlock": "earliest",
"toBlock": "latest",
"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}]}
@@ -180,8 +180,7 @@ defmodule Explorer.EthRPC do
"topics" => topics,
"transactionHash" => to_string(log.transaction_hash),
"transactionIndex" => log.transaction_index,
- "transactionLogIndex" => log.index,
- "type" => "mined"
+ "transactionLogIndex" => log.index
}
end
@@ -296,17 +295,14 @@ defmodule Explorer.EthRPC do
defp paging_options(%{
"paging_options" => %{
"logIndex" => log_index,
- "transactionIndex" => transaction_index,
"blockNumber" => block_number
}
- })
- when is_integer(transaction_index) do
+ }) do
with {:ok, parsed_block_number} <- to_number(block_number, "invalid block number"),
{:ok, parsed_log_index} <- to_number(log_index, "invalid log index") do
{:ok,
%{
log_index: parsed_log_index,
- transaction_index: transaction_index,
block_number: parsed_block_number
}}
end
diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex
index 604bfae66fc2..023f74e9dadb 100644
--- a/apps/explorer/lib/explorer/etherscan.ex
+++ b/apps/explorer/lib/explorer/etherscan.ex
@@ -4,11 +4,12 @@ defmodule Explorer.Etherscan do
"""
import Ecto.Query, only: [from: 2, where: 3, or_where: 3, union: 2, subquery: 1, order_by: 3]
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
alias Explorer.Etherscan.Logs
alias Explorer.{Chain, Repo}
alias Explorer.Chain.Address.{CurrentTokenBalance, TokenBalance}
- alias Explorer.Chain.{Address, Block, Hash, InternalTransaction, TokenTransfer, Transaction}
+ alias Explorer.Chain.{Address, Block, DenormalizationHelper, Hash, InternalTransaction, TokenTransfer, Transaction}
alias Explorer.Chain.Transaction.History.TransactionStats
@default_options %{
@@ -21,8 +22,6 @@ defmodule Explorer.Etherscan do
end_timestamp: nil
}
- @burn_address_hash_str "0x0000000000000000000000000000000000000000"
-
@doc """
Returns the maximum allowed page size number.
@@ -102,18 +101,32 @@ defmodule Explorer.Etherscan do
@spec list_internal_transactions(Hash.Full.t()) :: [map()]
def list_internal_transactions(%Hash{byte_count: unquote(Hash.Full.byte_count())} = transaction_hash) do
query =
- from(
- it in InternalTransaction,
- inner_join: t in assoc(it, :transaction),
- inner_join: b in assoc(t, :block),
- where: it.transaction_hash == ^transaction_hash,
- limit: 10_000,
- select:
- merge(map(it, ^@internal_transaction_fields), %{
- block_timestamp: b.timestamp,
- block_number: b.number
- })
- )
+ if DenormalizationHelper.denormalization_finished?() do
+ from(
+ it in InternalTransaction,
+ inner_join: transaction in assoc(it, :transaction),
+ where: it.transaction_hash == ^transaction_hash,
+ limit: 10_000,
+ select:
+ merge(map(it, ^@internal_transaction_fields), %{
+ block_timestamp: transaction.block_timestamp,
+ block_number: transaction.block_number
+ })
+ )
+ else
+ from(
+ it in InternalTransaction,
+ inner_join: t in assoc(it, :transaction),
+ inner_join: b in assoc(t, :block),
+ where: it.transaction_hash == ^transaction_hash,
+ limit: 10_000,
+ select:
+ merge(map(it, ^@internal_transaction_fields), %{
+ block_timestamp: b.timestamp,
+ block_number: b.number
+ })
+ )
+ end
query
|> Chain.where_transaction_has_multiple_internal_transactions()
@@ -159,8 +172,8 @@ defmodule Explorer.Etherscan do
query =
from(
it in InternalTransaction,
- inner_join: b in subquery(consensus_blocks),
- on: it.block_number == b.number,
+ inner_join: block in subquery(consensus_blocks),
+ on: it.block_number == block.number,
order_by: [
{^options.order_by_direction, it.block_number},
{:desc, it.transaction_index},
@@ -170,8 +183,8 @@ defmodule Explorer.Etherscan do
offset: ^offset(options),
select:
merge(map(it, ^@internal_transaction_fields), %{
- block_timestamp: b.timestamp,
- block_number: b.number
+ block_timestamp: block.timestamp,
+ block_number: block.number
})
)
@@ -213,19 +226,34 @@ defmodule Explorer.Etherscan do
|> Repo.replica().all()
else
query =
- from(
- it in InternalTransaction,
- inner_join: t in assoc(it, :transaction),
- inner_join: b in assoc(t, :block),
- order_by: [{^options.order_by_direction, t.block_number}],
- limit: ^options.page_size,
- offset: ^offset(options),
- select:
- merge(map(it, ^@internal_transaction_fields), %{
- block_timestamp: b.timestamp,
- block_number: b.number
- })
- )
+ if DenormalizationHelper.denormalization_finished?() do
+ from(
+ it in InternalTransaction,
+ inner_join: transaction in assoc(it, :transaction),
+ order_by: [{^options.order_by_direction, transaction.block_number}],
+ limit: ^options.page_size,
+ offset: ^offset(options),
+ select:
+ merge(map(it, ^@internal_transaction_fields), %{
+ block_timestamp: transaction.block_timestamp,
+ block_number: transaction.block_number
+ })
+ )
+ else
+ from(
+ it in InternalTransaction,
+ inner_join: t in assoc(it, :transaction),
+ inner_join: b in assoc(t, :block),
+ order_by: [{^options.order_by_direction, t.block_number}],
+ limit: ^options.page_size,
+ offset: ^offset(options),
+ select:
+ merge(map(it, ^@internal_transaction_fields), %{
+ block_timestamp: b.timestamp,
+ block_number: b.number
+ })
+ )
+ end
query
|> Chain.where_transaction_has_multiple_internal_transactions()
@@ -280,14 +308,14 @@ defmodule Explorer.Etherscan do
query =
from(
- b in Block,
- where: b.miner_hash == ^address_hash,
- order_by: [desc: b.number],
+ block in Block,
+ where: block.miner_hash == ^address_hash,
+ order_by: [desc: block.number],
limit: ^merged_options.page_size,
offset: ^offset(merged_options),
select: %{
- number: b.number,
- timestamp: b.timestamp
+ number: block.number,
+ timestamp: block.timestamp
}
)
@@ -344,6 +372,8 @@ defmodule Explorer.Etherscan do
@transaction_fields ~w(
block_hash
block_number
+ block_consensus
+ block_timestamp
created_contract_address_hash
cumulative_gas_used
from_address_hash
@@ -394,23 +424,36 @@ defmodule Explorer.Etherscan do
defp list_transactions(address_hash, max_block_number, options) do
query =
- from(
- t in Transaction,
- inner_join: b in assoc(t, :block),
- order_by: [{^options.order_by_direction, t.block_number}],
- limit: ^options.page_size,
- offset: ^offset(options),
- select:
- merge(map(t, ^@transaction_fields), %{
- block_timestamp: b.timestamp,
- confirmations: fragment("? - ?", ^max_block_number, t.block_number)
- })
- )
+ if DenormalizationHelper.denormalization_finished?() do
+ from(
+ t in Transaction,
+ order_by: [{^options.order_by_direction, t.block_number}],
+ limit: ^options.page_size,
+ offset: ^offset(options),
+ select:
+ merge(map(t, ^@transaction_fields), %{
+ confirmations: fragment("? - ?", ^max_block_number, t.block_number)
+ })
+ )
+ else
+ from(
+ t in Transaction,
+ inner_join: b in assoc(t, :block),
+ order_by: [{^options.order_by_direction, t.block_number}],
+ limit: ^options.page_size,
+ offset: ^offset(options),
+ select:
+ merge(map(t, ^@transaction_fields), %{
+ block_timestamp: b.timestamp,
+ confirmations: fragment("? - ?", ^max_block_number, t.block_number)
+ })
+ )
+ end
query
|> where_address_match(address_hash, options)
- |> where_start_block_match(options)
- |> where_end_block_match(options)
+ |> where_start_transaction_block_match(options)
+ |> where_end_transaction_block_match(options)
|> where_start_timestamp_match(options)
|> where_end_timestamp_match(options)
|> Repo.replica().all()
@@ -468,42 +511,76 @@ defmodule Explorer.Etherscan do
|> where_contract_address_match(contract_address_hash)
wrapped_query =
- from(
- tt in subquery(tt_specific_token_query),
- inner_join: t in Transaction,
- on: tt.transaction_hash == t.hash and tt.block_number == t.block_number and tt.block_hash == t.block_hash,
- inner_join: b in assoc(t, :block),
- order_by: [{^options.order_by_direction, tt.block_number}, {^options.order_by_direction, tt.token_log_index}],
- select: %{
- token_contract_address_hash: tt.token_contract_address_hash,
- transaction_hash: tt.transaction_hash,
- from_address_hash: tt.from_address_hash,
- to_address_hash: tt.to_address_hash,
- amount: tt.amount,
- amounts: tt.amounts,
- transaction_nonce: t.nonce,
- transaction_index: t.index,
- transaction_gas: t.gas,
- transaction_gas_price: t.gas_price,
- transaction_gas_used: t.gas_used,
- transaction_cumulative_gas_used: t.cumulative_gas_used,
- transaction_input: t.input,
- block_hash: b.hash,
- block_number: b.number,
- block_timestamp: b.timestamp,
- confirmations: fragment("? - ?", ^block_height, t.block_number),
- token_ids: tt.token_ids,
- token_name: tt.token_name,
- token_symbol: tt.token_symbol,
- token_decimals: tt.token_decimals,
- token_type: tt.token_type,
- token_log_index: tt.token_log_index
- }
- )
+ if DenormalizationHelper.denormalization_finished?() do
+ from(
+ tt in subquery(tt_specific_token_query),
+ inner_join: t in Transaction,
+ on: tt.transaction_hash == t.hash and tt.block_number == t.block_number and tt.block_hash == t.block_hash,
+ order_by: [{^options.order_by_direction, tt.block_number}, {^options.order_by_direction, tt.token_log_index}],
+ select: %{
+ token_contract_address_hash: tt.token_contract_address_hash,
+ transaction_hash: tt.transaction_hash,
+ from_address_hash: tt.from_address_hash,
+ to_address_hash: tt.to_address_hash,
+ amount: tt.amount,
+ amounts: tt.amounts,
+ transaction_nonce: t.nonce,
+ transaction_index: t.index,
+ transaction_gas: t.gas,
+ transaction_gas_price: t.gas_price,
+ transaction_gas_used: t.gas_used,
+ transaction_cumulative_gas_used: t.cumulative_gas_used,
+ transaction_input: t.input,
+ block_hash: t.block_hash,
+ block_number: t.block_number,
+ block_timestamp: t.block_timestamp,
+ confirmations: fragment("? - ?", ^block_height, t.block_number),
+ token_ids: tt.token_ids,
+ token_name: tt.token_name,
+ token_symbol: tt.token_symbol,
+ token_decimals: tt.token_decimals,
+ token_type: tt.token_type,
+ token_log_index: tt.token_log_index
+ }
+ )
+ else
+ from(
+ tt in subquery(tt_specific_token_query),
+ inner_join: t in Transaction,
+ on: tt.transaction_hash == t.hash and tt.block_number == t.block_number and tt.block_hash == t.block_hash,
+ inner_join: b in assoc(t, :block),
+ order_by: [{^options.order_by_direction, tt.block_number}, {^options.order_by_direction, tt.token_log_index}],
+ select: %{
+ token_contract_address_hash: tt.token_contract_address_hash,
+ transaction_hash: tt.transaction_hash,
+ from_address_hash: tt.from_address_hash,
+ to_address_hash: tt.to_address_hash,
+ amount: tt.amount,
+ amounts: tt.amounts,
+ transaction_nonce: t.nonce,
+ transaction_index: t.index,
+ transaction_gas: t.gas,
+ transaction_gas_price: t.gas_price,
+ transaction_gas_used: t.gas_used,
+ transaction_cumulative_gas_used: t.cumulative_gas_used,
+ transaction_input: t.input,
+ block_hash: b.hash,
+ block_number: b.number,
+ block_timestamp: b.timestamp,
+ confirmations: fragment("? - ?", ^block_height, t.block_number),
+ token_ids: tt.token_ids,
+ token_name: tt.token_name,
+ token_symbol: tt.token_symbol,
+ token_decimals: tt.token_decimals,
+ token_type: tt.token_type,
+ token_log_index: tt.token_log_index
+ }
+ )
+ end
wrapped_query
- |> where_start_block_match(options)
- |> where_end_block_match(options)
+ |> where_start_transaction_block_match(options)
+ |> where_end_transaction_block_match(options)
|> Repo.replica().all()
end
@@ -519,16 +596,44 @@ defmodule Explorer.Etherscan do
where(query, [..., block], block.number <= ^end_block)
end
+ defp where_start_transaction_block_match(query, %{start_block: nil}), do: query
+
+ defp where_start_transaction_block_match(query, %{start_block: start_block} = params) do
+ if DenormalizationHelper.denormalization_finished?() do
+ where(query, [transaction], transaction.block_number >= ^start_block)
+ else
+ where_start_block_match(query, params)
+ end
+ end
+
+ defp where_end_transaction_block_match(query, %{end_block: nil}), do: query
+
+ defp where_end_transaction_block_match(query, %{end_block: end_block} = params) do
+ if DenormalizationHelper.denormalization_finished?() do
+ where(query, [transaction], transaction.block_number <= ^end_block)
+ else
+ where_end_block_match(query, params)
+ end
+ end
+
defp where_start_timestamp_match(query, %{start_timestamp: nil}), do: query
defp where_start_timestamp_match(query, %{start_timestamp: start_timestamp}) do
- where(query, [..., block], ^start_timestamp <= block.timestamp)
+ if DenormalizationHelper.denormalization_finished?() do
+ where(query, [transaction], ^start_timestamp <= transaction.block_timestamp)
+ else
+ where(query, [..., block], ^start_timestamp <= block.timestamp)
+ end
end
defp where_end_timestamp_match(query, %{end_timestamp: nil}), do: query
defp where_end_timestamp_match(query, %{end_timestamp: end_timestamp}) do
- where(query, [..., block], block.timestamp <= ^end_timestamp)
+ if DenormalizationHelper.denormalization_finished?() do
+ where(query, [transaction], transaction.block_timestamp <= ^end_timestamp)
+ else
+ where(query, [..., block], block.timestamp <= ^end_timestamp)
+ end
end
defp where_contract_address_match(query, nil), do: query
@@ -585,7 +690,7 @@ defmodule Explorer.Etherscan do
@spec fetch_sum_coin_total_supply_minus_burnt() :: non_neg_integer
def fetch_sum_coin_total_supply_minus_burnt do
- {:ok, burn_address_hash} = Chain.string_to_address_hash(@burn_address_hash_str)
+ {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string())
query =
from(
diff --git a/apps/explorer/lib/explorer/etherscan/blocks.ex b/apps/explorer/lib/explorer/etherscan/blocks.ex
index d7238fb7a20f..0249f46743d3 100644
--- a/apps/explorer/lib/explorer/etherscan/blocks.ex
+++ b/apps/explorer/lib/explorer/etherscan/blocks.ex
@@ -11,7 +11,7 @@ defmodule Explorer.Etherscan.Blocks do
]
alias Explorer.{Chain, Repo}
- alias Explorer.Chain.{Address.CoinBalance, Block, Hash, Wei}
+ alias Explorer.Chain.{Address, Address.CoinBalance, Block, Hash, Wei}
@doc """
Returns the balance of the given address and block combination.
@@ -39,12 +39,16 @@ defmodule Explorer.Etherscan.Blocks do
end
def get_balance_as_of_block(address, :latest) do
- case Chain.max_consensus_block_number() do
- {:ok, latest_block_number} ->
- get_balance_as_of_block(address, latest_block_number)
+ latest_coin_balance_query =
+ from(
+ a in Address,
+ select: a.fetched_coin_balance,
+ where: a.hash == ^address
+ )
- {:error, :not_found} ->
- {:error, :not_found}
+ case Repo.replica().one(latest_coin_balance_query) do
+ nil -> {:error, :not_found}
+ coin_balance -> {:ok, coin_balance}
end
end
diff --git a/apps/explorer/lib/explorer/etherscan/contracts.ex b/apps/explorer/lib/explorer/etherscan/contracts.ex
index 4985220bf64f..de63dc05af36 100644
--- a/apps/explorer/lib/explorer/etherscan/contracts.ex
+++ b/apps/explorer/lib/explorer/etherscan/contracts.ex
@@ -11,8 +11,10 @@ defmodule Explorer.Etherscan.Contracts do
where: 3
]
- alias Explorer.{Chain, Repo}
+ alias Explorer.Repo
alias Explorer.Chain.{Address, Hash, SmartContract}
+ alias Explorer.Chain.SmartContract.Proxy
+ alias Explorer.Chain.SmartContract.Proxy.EIP1167
@spec address_hash_to_address_with_source_code(Hash.Address.t()) :: Address.t() | nil
def address_hash_to_address_with_source_code(address_hash) do
@@ -38,8 +40,8 @@ defmodule Explorer.Etherscan.Contracts do
}
else
address_verified_twin_contract =
- Chain.get_minimal_proxy_template(address_hash) ||
- Chain.get_address_verified_twin_contract(address_hash).verified_contract
+ EIP1167.get_implementation_address(address_hash) ||
+ SmartContract.get_address_verified_twin_contract(address_hash).verified_contract
compose_address_with_smart_contract(
address_with_smart_contract,
@@ -67,7 +69,7 @@ defmodule Explorer.Etherscan.Contracts do
def append_proxy_info(%Address{smart_contract: smart_contract} = address) when not is_nil(smart_contract) do
updated_smart_contract =
- if SmartContract.proxy_contract?(smart_contract) do
+ if Proxy.proxy_contract?(smart_contract) do
smart_contract
|> Map.put(:is_proxy, true)
|> Map.put(
diff --git a/apps/explorer/lib/explorer/etherscan/logs.ex b/apps/explorer/lib/explorer/etherscan/logs.ex
index 006c0a2e191b..a0c432ce4432 100644
--- a/apps/explorer/lib/explorer/etherscan/logs.ex
+++ b/apps/explorer/lib/explorer/etherscan/logs.ex
@@ -5,10 +5,10 @@ defmodule Explorer.Etherscan.Logs do
"""
- import Ecto.Query, only: [from: 2, where: 3, subquery: 1, order_by: 3, union: 2]
+ import Ecto.Query, only: [from: 2, limit: 2, where: 3, subquery: 1, order_by: 3, union: 2]
alias Explorer.{Chain, Repo}
- alias Explorer.Chain.{Block, InternalTransaction, Log, Transaction}
+ alias Explorer.Chain.{Block, DenormalizationHelper, InternalTransaction, Log, Transaction}
@base_filter %{
from_block: nil,
@@ -34,11 +34,10 @@ defmodule Explorer.Etherscan.Logs do
:fourth_topic,
:index,
:address_hash,
- :transaction_hash,
- :type
+ :transaction_hash
]
- @default_paging_options %{block_number: nil, transaction_index: nil, log_index: nil}
+ @default_paging_options %{block_number: nil, log_index: nil}
@doc """
Gets a list of logs that meet the criteria in a given filter map.
@@ -76,77 +75,126 @@ defmodule Explorer.Etherscan.Logs do
paging_options = if is_nil(paging_options), do: @default_paging_options, else: paging_options
prepared_filter = Map.merge(@base_filter, filter)
- logs_query = where_topic_match(Log, prepared_filter)
-
- query_to_address_hash_wrapped =
- logs_query
- |> internal_transaction_query(:to_address_hash, prepared_filter, address_hash)
- |> Chain.wrapped_union_subquery()
-
- query_from_address_hash_wrapped =
- logs_query
- |> internal_transaction_query(:from_address_hash, prepared_filter, address_hash)
- |> Chain.wrapped_union_subquery()
-
- query_created_contract_address_hash_wrapped =
- logs_query
- |> internal_transaction_query(:created_contract_address_hash, prepared_filter, address_hash)
- |> Chain.wrapped_union_subquery()
-
- internal_transaction_log_query =
- query_to_address_hash_wrapped
- |> union(^query_from_address_hash_wrapped)
- |> union(^query_created_contract_address_hash_wrapped)
+ if DenormalizationHelper.denormalization_finished?() do
+ logs_query =
+ Log
+ |> where_topic_match(prepared_filter)
+ |> where([log], log.address_hash == ^address_hash)
+ |> where([log], log.block_number >= ^prepared_filter.from_block)
+ |> where([log], log.block_number <= ^prepared_filter.to_block)
+ |> limit(1000)
+ |> order_by([log], asc: log.block_number, asc: log.index)
+ |> page_logs(paging_options)
+
+ all_transaction_logs_query =
+ from(log in subquery(logs_query),
+ join: transaction in Transaction,
+ on: log.transaction_hash == transaction.hash,
+ select: map(log, ^@log_fields),
+ select_merge: %{
+ gas_price: transaction.gas_price,
+ gas_used: transaction.gas_used,
+ transaction_index: transaction.index,
+ block_hash: transaction.block_hash,
+ block_number: transaction.block_number,
+ block_timestamp: transaction.block_timestamp,
+ block_consensus: transaction.block_consensus
+ }
+ )
- all_transaction_logs_query =
- from(transaction in Transaction,
- join: log in ^logs_query,
- on: log.transaction_hash == transaction.hash,
- where: transaction.block_number >= ^prepared_filter.from_block,
- where: transaction.block_number <= ^prepared_filter.to_block,
- where:
- transaction.to_address_hash == ^address_hash or
- transaction.from_address_hash == ^address_hash or
- transaction.created_contract_address_hash == ^address_hash,
- select: map(log, ^@log_fields),
- select_merge: %{
- gas_price: transaction.gas_price,
- gas_used: transaction.gas_used,
- transaction_index: transaction.index,
- block_number: transaction.block_number
- },
- union: ^internal_transaction_log_query
- )
+ query_with_blocks =
+ from(log_transaction_data in subquery(all_transaction_logs_query),
+ where: log_transaction_data.address_hash == ^address_hash,
+ order_by: log_transaction_data.block_number,
+ select_merge: %{
+ block_consensus: log_transaction_data.block_consensus
+ }
+ )
- query_with_blocks =
- from(log_transaction_data in subquery(all_transaction_logs_query),
- join: block in Block,
- on: block.number == log_transaction_data.block_number,
- where: log_transaction_data.address_hash == ^address_hash,
- order_by: block.number,
- limit: 1000,
- select_merge: %{
- transaction_index: log_transaction_data.transaction_index,
- block_hash: block.hash,
- block_number: block.number,
- block_timestamp: block.timestamp,
- block_consensus: block.consensus
- }
- )
+ query_with_consensus =
+ if Map.get(filter, :allow_non_consensus) do
+ query_with_blocks
+ else
+ from([transaction] in query_with_blocks,
+ where: transaction.block_consensus == true
+ )
+ end
+
+ query_with_consensus
+ |> Repo.replica().all()
+ else
+ logs_query = where_topic_match(Log, prepared_filter)
+
+ query_to_address_hash_wrapped =
+ logs_query
+ |> internal_transaction_query(:to_address_hash, prepared_filter, address_hash)
+ |> Chain.wrapped_union_subquery()
+
+ query_from_address_hash_wrapped =
+ logs_query
+ |> internal_transaction_query(:from_address_hash, prepared_filter, address_hash)
+ |> Chain.wrapped_union_subquery()
+
+ query_created_contract_address_hash_wrapped =
+ logs_query
+ |> internal_transaction_query(:created_contract_address_hash, prepared_filter, address_hash)
+ |> Chain.wrapped_union_subquery()
+
+ internal_transaction_log_query =
+ query_to_address_hash_wrapped
+ |> union(^query_from_address_hash_wrapped)
+ |> union(^query_created_contract_address_hash_wrapped)
+
+ all_transaction_logs_query =
+ from(transaction in Transaction,
+ join: log in ^logs_query,
+ on: log.transaction_hash == transaction.hash,
+ where: transaction.block_number >= ^prepared_filter.from_block,
+ where: transaction.block_number <= ^prepared_filter.to_block,
+ where:
+ transaction.to_address_hash == ^address_hash or
+ transaction.from_address_hash == ^address_hash or
+ transaction.created_contract_address_hash == ^address_hash,
+ select: map(log, ^@log_fields),
+ select_merge: %{
+ gas_price: transaction.gas_price,
+ gas_used: transaction.gas_used,
+ transaction_index: transaction.index,
+ block_number: transaction.block_number
+ },
+ union: ^internal_transaction_log_query
+ )
- query_with_consensus =
- if Map.get(filter, :allow_non_consensus) do
- query_with_blocks
- else
- from([_, block] in query_with_blocks,
- where: block.consensus == true
+ query_with_blocks =
+ from(log_transaction_data in subquery(all_transaction_logs_query),
+ join: block in Block,
+ on: block.number == log_transaction_data.block_number,
+ where: log_transaction_data.address_hash == ^address_hash,
+ order_by: block.number,
+ limit: 1000,
+ select_merge: %{
+ transaction_index: log_transaction_data.transaction_index,
+ block_hash: block.hash,
+ block_number: block.number,
+ block_timestamp: block.timestamp,
+ block_consensus: block.consensus
+ }
)
- end
- query_with_consensus
- |> order_by([log], asc: log.index)
- |> page_logs(paging_options)
- |> Repo.replica().all()
+ query_with_consensus =
+ if Map.get(filter, :allow_non_consensus) do
+ query_with_blocks
+ else
+ from([_, block] in query_with_blocks,
+ where: block.consensus == true
+ )
+ end
+
+ query_with_consensus
+ |> order_by([log], asc: log.index)
+ |> page_logs(paging_options)
+ |> Repo.replica().all()
+ end
end
# Since address_hash was not present, we know that a
@@ -156,49 +204,90 @@ defmodule Explorer.Etherscan.Logs do
def list_logs(filter, paging_options) do
paging_options = if is_nil(paging_options), do: @default_paging_options, else: paging_options
prepared_filter = Map.merge(@base_filter, filter)
-
logs_query = where_topic_match(Log, prepared_filter)
- block_transaction_query =
- from(transaction in Transaction,
- join: block in assoc(transaction, :block),
- where: block.number >= ^prepared_filter.from_block,
- where: block.number <= ^prepared_filter.to_block,
- select: %{
- transaction_hash: transaction.hash,
- gas_price: transaction.gas_price,
- gas_used: transaction.gas_used,
- transaction_index: transaction.index,
- block_hash: block.hash,
- block_number: block.number,
- block_timestamp: block.timestamp,
- block_consensus: block.consensus
- }
- )
+ if DenormalizationHelper.denormalization_finished?() do
+ block_transaction_query =
+ from(transaction in Transaction,
+ where: transaction.block_number >= ^prepared_filter.from_block,
+ where: transaction.block_number <= ^prepared_filter.to_block,
+ select: %{
+ transaction_hash: transaction.hash,
+ gas_price: transaction.gas_price,
+ gas_used: transaction.gas_used,
+ transaction_index: transaction.index,
+ block_hash: transaction.block_hash,
+ block_number: transaction.block_number,
+ block_timestamp: transaction.block_timestamp,
+ block_consensus: transaction.block_consensus
+ }
+ )
- query_with_consensus =
- if Map.get(filter, :allow_non_consensus) do
- block_transaction_query
- else
- from([_, block] in block_transaction_query,
- where: block.consensus == true
+ query_with_consensus =
+ if Map.get(filter, :allow_non_consensus) do
+ block_transaction_query
+ else
+ from([transaction] in block_transaction_query,
+ where: transaction.block_consensus == true
+ )
+ end
+
+ query_with_block_transaction_data =
+ from(log in logs_query,
+ join: block_transaction_data in subquery(query_with_consensus),
+ on: block_transaction_data.transaction_hash == log.transaction_hash,
+ order_by: block_transaction_data.block_number,
+ limit: 1000,
+ select: block_transaction_data,
+ select_merge: map(log, ^@log_fields)
)
- end
-
- query_with_block_transaction_data =
- from(log in logs_query,
- join: block_transaction_data in subquery(query_with_consensus),
- on: block_transaction_data.transaction_hash == log.transaction_hash,
- order_by: block_transaction_data.block_number,
- limit: 1000,
- select: block_transaction_data,
- select_merge: map(log, ^@log_fields)
- )
- query_with_block_transaction_data
- |> order_by([log], asc: log.index)
- |> page_logs(paging_options)
- |> Repo.replica().all()
+ query_with_block_transaction_data
+ |> order_by([log], asc: log.index)
+ |> page_logs(paging_options)
+ |> Repo.replica().all()
+ else
+ block_transaction_query =
+ from(transaction in Transaction,
+ join: block in assoc(transaction, :block),
+ where: block.number >= ^prepared_filter.from_block,
+ where: block.number <= ^prepared_filter.to_block,
+ select: %{
+ transaction_hash: transaction.hash,
+ gas_price: transaction.gas_price,
+ gas_used: transaction.gas_used,
+ transaction_index: transaction.index,
+ block_hash: block.hash,
+ block_number: block.number,
+ block_timestamp: block.timestamp,
+ block_consensus: block.consensus
+ }
+ )
+
+ query_with_consensus =
+ if Map.get(filter, :allow_non_consensus) do
+ block_transaction_query
+ else
+ from([_, block] in block_transaction_query,
+ where: block.consensus == true
+ )
+ end
+
+ query_with_block_transaction_data =
+ from(log in logs_query,
+ join: block_transaction_data in subquery(query_with_consensus),
+ on: block_transaction_data.transaction_hash == log.transaction_hash,
+ order_by: block_transaction_data.block_number,
+ limit: 1000,
+ select: block_transaction_data,
+ select_merge: map(log, ^@log_fields)
+ )
+
+ query_with_block_transaction_data
+ |> order_by([log], asc: log.index)
+ |> page_logs(paging_options)
+ |> Repo.replica().all()
+ end
end
@topics [
@@ -248,16 +337,14 @@ defmodule Explorer.Etherscan.Logs do
defp where_multiple_topics_match(query, _, _, _), do: query
- defp page_logs(query, %{block_number: nil, transaction_index: nil, log_index: nil}) do
+ defp page_logs(query, %{block_number: nil, log_index: nil}) do
query
end
- defp page_logs(query, %{block_number: block_number, transaction_index: transaction_index, log_index: log_index}) do
+ defp page_logs(query, %{block_number: block_number, log_index: log_index}) do
from(
data in query,
- where:
- data.index > ^log_index and data.block_number >= ^block_number and
- data.transaction_index >= ^transaction_index
+ where: data.index > ^log_index and data.block_number >= ^block_number
)
end
diff --git a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex
index 93155d93b851..b3497d8b4c75 100644
--- a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex
+++ b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex
@@ -10,6 +10,7 @@ defmodule Explorer.ExchangeRates do
require Logger
alias Explorer.Chain.Events.Publisher
+ alias Explorer.Market
alias Explorer.ExchangeRates.{Source, Token}
@interval Application.compile_env(:explorer, __MODULE__)[:cache_period]
@@ -84,9 +85,11 @@ defmodule Explorer.ExchangeRates do
@doc """
Lists exchange rates for the tracked tickers.
"""
- @spec list :: [Token.t()]
+ @spec list :: [Token.t()] | nil
def list do
- list_from_store(store())
+ if enabled?() do
+ list_from_store(store())
+ end
end
@doc """
@@ -121,10 +124,29 @@ defmodule Explorer.ExchangeRates do
@spec fetch_rates :: Task.t()
defp fetch_rates do
Task.Supervisor.async_nolink(Explorer.MarketTaskSupervisor, fn ->
- Source.fetch_exchange_rates()
+ case Source.fetch_exchange_rates() do
+ {:ok, tokens} -> {:ok, add_coin_info_from_db(tokens)}
+ err -> err
+ end
end)
end
+ defp add_coin_info_from_db(tokens) do
+ case Market.fetch_recent_history() do
+ [today | _the_rest] ->
+ tvl_from_history = Map.get(today, :tvl)
+
+ tokens
+ |> Enum.map(fn
+ %Token{tvl_usd: nil} = token -> %{token | tvl_usd: tvl_from_history}
+ token -> token
+ end)
+
+ _ ->
+ tokens
+ end
+ end
+
defp list_from_store(:ets) do
table_name()
|> :ets.tab2list()
diff --git a/apps/explorer/lib/explorer/exchange_rates/source.ex b/apps/explorer/lib/explorer/exchange_rates/source.ex
index 34d68c5154c4..0c7a0929651f 100644
--- a/apps/explorer/lib/explorer/exchange_rates/source.ex
+++ b/apps/explorer/lib/explorer/exchange_rates/source.ex
@@ -6,6 +6,7 @@ defmodule Explorer.ExchangeRates.Source do
alias Explorer.Chain.Hash
alias Explorer.ExchangeRates.Source.CoinGecko
alias Explorer.ExchangeRates.Token
+ alias Explorer.Helper
alias HTTPoison.{Error, Response}
@doc """
@@ -40,6 +41,22 @@ defmodule Explorer.ExchangeRates.Source do
fetch_exchange_rates_request(CoinGecko, source_url, headers)
end
+ @spec fetch_token_hashes_with_market_data :: {:ok, [String.t()]} | {:error, any}
+ def fetch_token_hashes_with_market_data do
+ source_url = CoinGecko.source_url(:coins_list)
+ headers = CoinGecko.headers()
+
+ case http_request(source_url, headers) do
+ {:ok, result} ->
+ {:ok,
+ result
+ |> CoinGecko.format_data()}
+
+ resp ->
+ resp
+ end
+ end
+
defp fetch_exchange_rates_request(_source, source_url, _headers) when is_nil(source_url),
do: {:error, "Source URL is nil"}
@@ -60,7 +77,7 @@ defmodule Explorer.ExchangeRates.Source do
@doc """
Callback for api's to format the data returned by their query.
"""
- @callback format_data(map()) :: [any]
+ @callback format_data(map() | list()) :: [any]
@doc """
Url for the api to query to get the market info.
@@ -75,12 +92,6 @@ defmodule Explorer.ExchangeRates.Source do
[{"Content-Type", "application/json"}]
end
- def decode_json(data) do
- Jason.decode!(data)
- rescue
- _ -> data
- end
-
def to_decimal(nil), do: nil
def to_decimal(%Decimal{} = value), do: value
@@ -109,7 +120,7 @@ defmodule Explorer.ExchangeRates.Source do
parse_http_success_response(body)
{:ok, %Response{body: body, status_code: status_code}} when status_code in 400..526 ->
- parse_http_error_response(body)
+ parse_http_error_response(body, status_code)
{:ok, %Response{status_code: status_code}} when status_code in 300..308 ->
{:error, "Source redirected"}
@@ -119,37 +130,22 @@ defmodule Explorer.ExchangeRates.Source do
{:error, %Error{reason: reason}} ->
{:error, reason}
-
- {:error, :nxdomain} ->
- {:error, "Source is not responsive"}
-
- {:error, _} ->
- {:error, "Source unknown response"}
end
end
defp parse_http_success_response(body) do
- body_json = decode_json(body)
-
- cond do
- is_map(body_json) ->
- {:ok, body_json}
+ body_json = Helper.decode_json(body)
- is_list(body_json) ->
- {:ok, body_json}
-
- true ->
- {:ok, body}
- end
+ {:ok, body_json}
end
- defp parse_http_error_response(body) do
- body_json = decode_json(body)
+ defp parse_http_error_response(body, status_code) do
+ body_json = Helper.decode_json(body)
if is_map(body_json) do
- {:error, body_json["error"]}
+ {:error, "#{status_code}: #{body_json["error"]}"}
else
- {:error, body}
+ {:error, "#{status_code}: #{body}"}
end
end
end
diff --git a/apps/explorer/lib/explorer/exchange_rates/source/coin_gecko.ex b/apps/explorer/lib/explorer/exchange_rates/source/coin_gecko.ex
index 2157e3328e44..1499e20e834b 100644
--- a/apps/explorer/lib/explorer/exchange_rates/source/coin_gecko.ex
+++ b/apps/explorer/lib/explorer/exchange_rates/source/coin_gecko.ex
@@ -35,6 +35,7 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do
id: id,
last_updated: last_updated,
market_cap_usd: to_decimal(market_cap_data_usd),
+ tvl_usd: nil,
name: json_data["name"],
symbol: String.upcase(json_data["symbol"]),
usd_value: current_price,
@@ -68,9 +69,36 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do
end)
end
+ @impl Source
+ def format_data(supported_coins) when is_list(supported_coins) do
+ platform = platform()
+
+ supported_coins
+ |> Enum.reduce([], fn
+ %{"platforms" => %{^platform => token_contract_hash_str}}, acc ->
+ case Chain.Hash.Address.cast(token_contract_hash_str) do
+ {:ok, token_contract_hash} -> [token_contract_hash | acc]
+ _ -> acc
+ end
+
+ _, acc ->
+ acc
+ end)
+ end
+
@impl Source
def format_data(_), do: []
+ @spec history_url(non_neg_integer()) :: String.t()
+ def history_url(previous_days) do
+ query_params = %{
+ "days" => previous_days,
+ "vs_currency" => "usd"
+ }
+
+ "#{source_url()}/market_chart?#{URI.encode_query(query_params)}"
+ end
+
@impl Source
def source_url do
explicit_coin_id = config(:coin_id)
@@ -91,6 +119,11 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do
if id, do: "#{base_url()}/coins/#{id}", else: nil
end
+ @impl Source
+ def source_url(:coins_list) do
+ "#{base_url()}/coins/list?include_platform=true"
+ end
+
@impl Source
def source_url(token_addresses) when is_list(token_addresses) do
joined_addresses = token_addresses |> Enum.map_join(",", &to_string/1)
@@ -130,6 +163,17 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do
end
end
+ @doc """
+ Converts date time string into DateTime object formatted as date
+ """
+ @spec date(String.t()) :: Date.t()
+ def date(date_time_string) do
+ with {:ok, datetime, _} <- DateTime.from_iso8601(date_time_string) do
+ datetime
+ |> DateTime.to_date()
+ end
+ end
+
defp api_key do
config(:api_key) || nil
end
@@ -141,22 +185,16 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do
end
def coin_id(symbol) do
- id_mapping = token_symbol_to_id_mapping_to_get_price(symbol)
+ url = "#{base_url()}/coins/list"
- if id_mapping do
- {:ok, id_mapping}
- else
- url = "#{base_url()}/coins/list"
-
- symbol_downcase = String.downcase(symbol)
+ symbol_downcase = String.downcase(symbol)
- case Source.http_request(url, headers()) do
- {:ok, data} ->
- get_symbol_data(data, symbol_downcase)
+ case Source.http_request(url, headers()) do
+ {:ok, data} ->
+ get_symbol_data(data, symbol_downcase)
- resp ->
- resp
- end
+ _ ->
+ {:ok, symbol_downcase}
end
end
@@ -266,12 +304,4 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do
defp config(key) do
Application.get_env(:explorer, __MODULE__, [])[key]
end
-
- defp token_symbol_to_id_mapping_to_get_price(symbol) do
- case symbol do
- "UNI" -> "uniswap"
- "SURF" -> "surf-finance"
- _symbol -> nil
- end
- end
end
diff --git a/apps/explorer/lib/explorer/exchange_rates/source/coin_market_cap.ex b/apps/explorer/lib/explorer/exchange_rates/source/coin_market_cap.ex
index b8b1b8fd753e..3f13d25ba464 100644
--- a/apps/explorer/lib/explorer/exchange_rates/source/coin_market_cap.ex
+++ b/apps/explorer/lib/explorer/exchange_rates/source/coin_market_cap.ex
@@ -18,7 +18,7 @@ defmodule Explorer.ExchangeRates.Source.CoinMarketCap do
last_updated = get_last_updated(token_properties)
current_price = get_current_price(token_properties)
- id = token_properties && token_properties["id"]
+ id = token_properties["id"]
btc_value =
if Application.get_env(:explorer, Explorer.ExchangeRates)[:fetch_btc_value],
@@ -40,8 +40,9 @@ defmodule Explorer.ExchangeRates.Source.CoinMarketCap do
id: id,
last_updated: last_updated,
market_cap_usd: to_decimal(market_cap_data_usd),
- name: token_properties && token_properties["name"],
- symbol: token_properties && String.upcase(token_properties["symbol"]),
+ tvl_usd: nil,
+ name: token_properties["name"],
+ symbol: String.upcase(token_properties["symbol"]),
usd_value: current_price,
volume_24h_usd: to_decimal(total_volume_data_usd)
}
@@ -55,8 +56,18 @@ defmodule Explorer.ExchangeRates.Source.CoinMarketCap do
def source_url do
coin = Explorer.coin()
symbol = if coin, do: String.upcase(Explorer.coin()), else: nil
+ coin_id = coin_id()
- if symbol, do: "#{api_quotes_latest_url()}?symbol=#{symbol}&CMC_PRO_API_KEY=#{api_key()}", else: nil
+ cond do
+ coin_id ->
+ "#{api_quotes_latest_url()}?id=#{coin_id}&CMC_PRO_API_KEY=#{api_key()}"
+
+ symbol ->
+ "#{api_quotes_latest_url()}?symbol=#{symbol}&CMC_PRO_API_KEY=#{api_key()}"
+
+ true ->
+ nil
+ end
end
@impl Source
@@ -84,45 +95,58 @@ defmodule Explorer.ExchangeRates.Source.CoinMarketCap do
config(:api_key)
end
- defp get_token_properties(market_data) do
- token_values_list =
- market_data
- |> Map.values()
-
- if Enum.count(token_values_list) > 0 do
- token_values = token_values_list |> Enum.at(0)
+ defp coin_id do
+ config(:coin_id)
+ end
- if Enum.count(token_values) > 0 do
+ @doc """
+ Extracts token properties from CoinMarketCap coin endpoint response
+ """
+ @spec get_token_properties(map()) :: map()
+ def get_token_properties(market_data) do
+ with token_values_list <- market_data |> Map.values(),
+ true <- Enum.count(token_values_list) > 0,
+ token_values <- token_values_list |> Enum.at(0),
+ true <- Enum.count(token_values) > 0 do
+ if is_list(token_values) do
token_values |> Enum.at(0)
else
- %{}
+ token_values
end
else
- %{}
+ _ -> %{}
end
end
defp get_circulating_supply(token_properties) do
- token_properties && token_properties["circulating_supply"]
+ token_properties["circulating_supply"]
end
defp get_total_supply(token_properties) do
- token_properties && token_properties["total_supply"]
+ token_properties["total_supply"]
end
- defp get_market_cap_data_usd(token_properties) do
- token_properties && token_properties["quote"] &&
+ @doc """
+ Extracts market cap in usd from token properties, which are returned in get_token_properties/1
+ """
+ @spec get_market_cap_data_usd(map()) :: String.t()
+ def get_market_cap_data_usd(token_properties) do
+ token_properties["quote"] &&
token_properties["quote"]["USD"] &&
token_properties["quote"]["USD"]["market_cap"]
end
defp get_total_volume_data_usd(token_properties) do
- token_properties && token_properties["quote"] &&
+ token_properties["quote"] &&
token_properties["quote"]["USD"] &&
token_properties["quote"]["USD"]["volume_24h"]
end
- defp get_last_updated(token_properties) do
+ @doc """
+ Extracts last updated from token properties, which are returned in get_token_properties/1
+ """
+ @spec get_last_updated(map()) :: DateTime.t()
+ def get_last_updated(token_properties) do
last_updated_data = token_properties && token_properties["last_updated"]
if last_updated_data do
@@ -133,8 +157,12 @@ defmodule Explorer.ExchangeRates.Source.CoinMarketCap do
end
end
- defp get_current_price(token_properties) do
- if token_properties && token_properties["quote"] && token_properties["quote"]["USD"] &&
+ @doc """
+ Extracts current price from token properties, which are returned in get_token_properties/1
+ """
+ @spec get_current_price(map()) :: String.t() | non_neg_integer()
+ def get_current_price(token_properties) do
+ if token_properties["quote"] && token_properties["quote"]["USD"] &&
token_properties["quote"]["USD"]["price"] do
to_decimal(token_properties["quote"]["USD"]["price"])
else
diff --git a/apps/explorer/lib/explorer/exchange_rates/source/defillama.ex b/apps/explorer/lib/explorer/exchange_rates/source/defillama.ex
new file mode 100644
index 000000000000..e7cae25e0292
--- /dev/null
+++ b/apps/explorer/lib/explorer/exchange_rates/source/defillama.ex
@@ -0,0 +1,46 @@
+defmodule Explorer.ExchangeRates.Source.DefiLlama do
+ @moduledoc """
+ Adapter for fetching exchange rates from https://defillama.com/
+
+ """
+
+ alias Explorer.ExchangeRates.Source
+
+ @behaviour Source
+
+ @impl Source
+ def format_data(_), do: []
+
+ @spec history_url(non_neg_integer()) :: String.t()
+ def history_url(_previous_days) do
+ "#{base_url()}/historicalChainTvl"
+ end
+
+ @impl Source
+ def source_url do
+ ""
+ end
+
+ @impl Source
+ def source_url(_) do
+ ""
+ end
+
+ @impl Source
+ def headers do
+ []
+ end
+
+ defp base_url do
+ base_free_url()
+ end
+
+ defp base_free_url do
+ config(:base_url) || "https://api.llama.fi/v2"
+ end
+
+ @spec config(atom()) :: term
+ defp config(key) do
+ Application.get_env(:explorer, __MODULE__, [])[key]
+ end
+end
diff --git a/apps/explorer/lib/explorer/exchange_rates/token.ex b/apps/explorer/lib/explorer/exchange_rates/token.ex
index 8aa6d3b721aa..07405701e8cc 100644
--- a/apps/explorer/lib/explorer/exchange_rates/token.ex
+++ b/apps/explorer/lib/explorer/exchange_rates/token.ex
@@ -11,28 +11,30 @@ defmodule Explorer.ExchangeRates.Token do
* `:btc_value` - The Bitcoin value of the currency
* `:id` - ID of a currency
* `:last_updated` - Timestamp of when the value was last updated
- * `:market_cap_usd` - Market capitalization of the currency
+ * `:market_cap_usd` - Market capitalization of the currency in USD
+ * `:tvl_usd` - Token value locked of the currency in USD
* `:name` - Human-readable name of a ticker
* `:symbol` - Trading symbol used to represent a currency
* `:usd_value` - The USD value of the currency
* `:volume_24h_usd` - The volume from the last 24 hours in USD
"""
@type t :: %__MODULE__{
- available_supply: Decimal.t(),
- total_supply: Decimal.t(),
- btc_value: Decimal.t(),
- id: String.t(),
- last_updated: DateTime.t(),
- market_cap_usd: Decimal.t(),
- name: String.t(),
- symbol: String.t(),
- usd_value: Decimal.t(),
- volume_24h_usd: Decimal.t()
+ available_supply: Decimal.t() | nil,
+ total_supply: Decimal.t() | nil,
+ btc_value: Decimal.t() | nil,
+ id: String.t() | nil,
+ last_updated: DateTime.t() | nil,
+ market_cap_usd: Decimal.t() | nil,
+ tvl_usd: Decimal.t() | nil,
+ name: String.t() | nil,
+ symbol: String.t() | nil,
+ usd_value: Decimal.t() | nil,
+ volume_24h_usd: Decimal.t() | nil
}
@derive Jason.Encoder
- @enforce_keys ~w(available_supply total_supply btc_value id last_updated market_cap_usd name symbol usd_value volume_24h_usd)a
- defstruct ~w(available_supply total_supply btc_value id last_updated market_cap_usd name symbol usd_value volume_24h_usd)a
+ @enforce_keys ~w(available_supply total_supply btc_value id last_updated market_cap_usd tvl_usd name symbol usd_value volume_24h_usd)a
+ defstruct ~w(available_supply total_supply btc_value id last_updated market_cap_usd tvl_usd name symbol usd_value volume_24h_usd)a
def null,
do: %__MODULE__{
@@ -44,6 +46,7 @@ defmodule Explorer.ExchangeRates.Token do
usd_value: nil,
volume_24h_usd: nil,
market_cap_usd: nil,
+ tvl_usd: nil,
btc_value: nil,
last_updated: nil
}
@@ -59,17 +62,18 @@ defmodule Explorer.ExchangeRates.Token do
usd_value: usd_value,
volume_24h_usd: volume_24h_usd,
market_cap_usd: market_cap_usd,
+ tvl_usd: tvl_usd,
btc_value: btc_value,
last_updated: last_updated
}) do
# symbol is first because it is the key used for lookup in `Explorer.ExchangeRates`'s ETS table
- {symbol, id, name, available_supply, total_supply, usd_value, volume_24h_usd, market_cap_usd, btc_value,
+ {symbol, id, name, available_supply, total_supply, usd_value, volume_24h_usd, market_cap_usd, tvl_usd, btc_value,
last_updated}
end
def from_tuple(
- {symbol, id, name, available_supply, total_supply, usd_value, volume_24h_usd, market_cap_usd, btc_value,
- last_updated}
+ {symbol, id, name, available_supply, total_supply, usd_value, volume_24h_usd, market_cap_usd, tvl_usd,
+ btc_value, last_updated}
) do
%__MODULE__{
symbol: symbol,
@@ -80,6 +84,7 @@ defmodule Explorer.ExchangeRates.Token do
usd_value: usd_value,
volume_24h_usd: volume_24h_usd,
market_cap_usd: market_cap_usd,
+ tvl_usd: tvl_usd,
btc_value: btc_value,
last_updated: last_updated
}
diff --git a/apps/explorer/lib/explorer/exchange_rates/token_exchange_rates.ex b/apps/explorer/lib/explorer/exchange_rates/token_exchange_rates.ex
index b1233d316ad0..2d3b9cc86db2 100644
--- a/apps/explorer/lib/explorer/exchange_rates/token_exchange_rates.ex
+++ b/apps/explorer/lib/explorer/exchange_rates/token_exchange_rates.ex
@@ -8,7 +8,6 @@ defmodule Explorer.ExchangeRates.TokenExchangeRates do
alias Explorer.ExchangeRates.Source
alias Explorer.{Chain.Token, Repo}
-
@batch_size 150
@interval :timer.seconds(5)
@refetch_interval :timer.hours(1)
@@ -16,7 +15,7 @@ defmodule Explorer.ExchangeRates.TokenExchangeRates do
defstruct max_batch_size: @batch_size,
interval: @interval,
refetch_interval: @refetch_interval,
- last_fetched_token_contract_address: nil
+ tokens_to_fetch: nil
@spec start_link(term()) :: GenServer.on_start()
def start_link(_) do
@@ -44,34 +43,61 @@ defmodule Explorer.ExchangeRates.TokenExchangeRates do
def handle_info(
:fetch,
%__MODULE__{
- max_batch_size: batch_size,
interval: interval,
refetch_interval: refetch_interval,
- last_fetched_token_contract_address: last_fetched
+ tokens_to_fetch: nil
} = state
) do
- tokens_to_update = last_fetched |> Token.tokens_to_update_fiat_value(batch_size) |> Repo.all()
+ case Source.fetch_token_hashes_with_market_data() do
+ {:ok, contract_address_hashes} ->
+ tokens = contract_address_hashes |> Token.tokens_by_contract_address_hashes() |> Repo.all()
+ Process.send_after(self(), :fetch, interval)
+ {:noreply, %{state | tokens_to_fetch: tokens}}
- case tokens_to_update |> Enum.map(& &1.contract_address_hash) |> Source.fetch_market_data_for_token_addresses() do
- {:ok, fiat_values} ->
- tokens_to_update
- |> Enum.each(&update_tokens(&1, fiat_values))
+ {:error, err} ->
+ Logger.error("Error while fetching tokens with market data (/coins/list): #{inspect(err)}")
+ Process.send_after(self(), :fetch, refetch_interval)
+ {:noreply, state}
+ end
+ end
+
+ @impl GenServer
+ def handle_info(
+ :fetch,
+ %__MODULE__{
+ refetch_interval: refetch_interval,
+ tokens_to_fetch: []
+ } = state
+ ) do
+ Process.send_after(self(), :fetch, refetch_interval)
+ {:noreply, %{state | tokens_to_fetch: nil}}
+ end
+
+ @impl GenServer
+ def handle_info(
+ :fetch,
+ %__MODULE__{
+ max_batch_size: batch_size,
+ interval: interval,
+ tokens_to_fetch: tokens_to_fetch
+ } = state
+ ) do
+ {fetch_now, fetch_later} = Enum.split(tokens_to_fetch, batch_size)
+
+ case fetch_now |> Enum.map(& &1.contract_address_hash) |> Source.fetch_market_data_for_token_addresses() do
+ {:ok, token_to_market_data} ->
+ fetch_now |> Enum.each(&update_token(&1, token_to_market_data))
err ->
Logger.error("Error while fetching fiat values for tokens: #{inspect(err)}")
end
- if length(tokens_to_update) < batch_size do
- Process.send_after(self(), :fetch, refetch_interval)
- {:noreply, %{state | last_fetched_token_contract_address: nil}}
- else
- Process.send_after(self(), :fetch, interval)
- {:noreply, %{state | last_fetched_token_contract_address: List.last(tokens_to_update).contract_address_hash}}
- end
+ Process.send_after(self(), :fetch, interval)
+ {:noreply, %{state | tokens_to_fetch: fetch_later}}
end
- defp update_tokens(%{contract_address_hash: contract_address_hash} = token, fiat_values) do
- case Map.get(fiat_values, contract_address_hash) do
+ defp update_token(%{contract_address_hash: contract_address_hash} = token, token_to_market_data) do
+ case Map.get(token_to_market_data, contract_address_hash) do
%{} = market_data ->
token
|> Token.changeset(market_data)
diff --git a/apps/explorer/lib/explorer/helper.ex b/apps/explorer/lib/explorer/helper.ex
index efbcfe156172..20d65314b23e 100644
--- a/apps/explorer/lib/explorer/helper.ex
+++ b/apps/explorer/lib/explorer/helper.ex
@@ -3,6 +3,34 @@ defmodule Explorer.Helper do
Common explorer helper
"""
+ alias ABI.TypeDecoder
+ alias Explorer.Chain
+ alias Explorer.Chain.Data
+
+ import Ecto.Query, only: [where: 3]
+
+ @spec decode_data(binary() | map(), list()) :: list() | nil
+ def decode_data("0x", types) do
+ for _ <- types, do: nil
+ end
+
+ def decode_data("0x" <> encoded_data, types) do
+ decode_data(encoded_data, types)
+ end
+
+ def decode_data(%Data{} = data, types) do
+ data
+ |> Data.to_string()
+ |> decode_data(types)
+ end
+
+ def decode_data(encoded_data, types) do
+ encoded_data
+ |> Base.decode16!(case: :mixed)
+ |> TypeDecoder.decode_raw(types)
+ end
+
+ @spec parse_integer(binary() | nil) :: integer() | nil
def parse_integer(nil), do: nil
def parse_integer(string) do
@@ -11,4 +39,57 @@ defmodule Explorer.Helper do
_ -> nil
end
end
+
+ @doc """
+ Function to preload a `struct` for each element of the `list`.
+ You should specify a primary key for a `struct` in `references_field`,
+ and the list element's foreign key in `foreign_key_field`.
+ Results will be placed to `preload_field`
+ """
+ @spec custom_preload(list(map()), keyword(), atom(), atom(), atom(), atom()) :: list()
+ def custom_preload(list, options, struct, foreign_key_field, references_field, preload_field) do
+ to_fetch_from_db = list |> Enum.map(& &1[foreign_key_field]) |> Enum.uniq()
+
+ associated_elements =
+ struct
+ |> where([t], field(t, ^references_field) in ^to_fetch_from_db)
+ |> Chain.select_repo(options).all()
+ |> Enum.reduce(%{}, fn el, acc -> Map.put(acc, Map.from_struct(el)[references_field], el) end)
+
+ Enum.map(list, fn el -> Map.put(el, preload_field, associated_elements[el[foreign_key_field]]) end)
+ end
+
+ @doc """
+ Decode json
+ """
+ @spec decode_json(any()) :: map() | list() | nil
+ def decode_json(nil), do: nil
+
+ def decode_json(data) do
+ if String.valid?(data) do
+ safe_decode_json(data)
+ else
+ data
+ |> :unicode.characters_to_binary(:latin1)
+ |> safe_decode_json()
+ end
+ end
+
+ defp safe_decode_json(data) do
+ case Jason.decode(data) do
+ {:ok, decoded} -> decoded
+ _ -> %{error: data}
+ end
+ end
+
+ @doc """
+ Tries to decode binary to json, return either decoded object, or initial binary
+ """
+ @spec maybe_decode(binary) :: any
+ def maybe_decode(data) do
+ case safe_decode_json(data) do
+ %{error: _} -> data
+ decoded -> decoded
+ end
+ end
end
diff --git a/apps/explorer/lib/explorer/market/history/cataloger.ex b/apps/explorer/lib/explorer/market/history/cataloger.ex
index 8545ffa159ba..f3c73de23d57 100644
--- a/apps/explorer/lib/explorer/market/history/cataloger.ex
+++ b/apps/explorer/lib/explorer/market/history/cataloger.ex
@@ -8,58 +8,121 @@ defmodule Explorer.Market.History.Cataloger do
source will follow exponential backoff `100ms * 2^(n+1)` where `n` is the
number of failed requests.
- ## Configuration
-
- The following example shows the configurable values in a sample config.
-
- config :explorer, Explorer.Market.History.Cataloger,
- # fetch interval in milliseconds
- history_fetch_interval: :timer.minutes(60),
- # Base backoff in milliseconds for failed requests to history API
- base_backoff: 100
-
"""
use GenServer
require Logger
+ alias Explorer.History.Process, as: HistoryProcess
alias Explorer.Market
- @typep milliseconds :: non_neg_integer()
+ @price_failed_attempts 10
+ @market_cap_failed_attempts 3
+ @tvl_failed_attempts 3
@impl GenServer
def init(:ok) do
- send(self(), {:fetch_history, 365})
+ if Application.get_env(:explorer, __MODULE__)[:enabled] do
+ send(self(), {:fetch_price_history, 365})
- {:ok, %{}}
+ {:ok, %{}}
+ else
+ :ignore
+ end
end
@impl GenServer
- def handle_info({:fetch_history, day_count}, state) do
- fetch_history(day_count)
+ def handle_info({:fetch_price_history, day_count}, state) do
+ fetch_price_history(day_count)
{:noreply, state}
end
@impl GenServer
# Record fetch successful.
- def handle_info({_ref, {_, _, {:ok, records}}}, state) do
- Market.bulk_insert_history(records)
+ def handle_info({_ref, {:price_history, {_, _, {:ok, records}}}}, state) do
+ Process.send(self(), {:fetch_market_cap_history, 365}, [])
+ state = state |> Map.put_new(:price_records, records)
- # Schedule next check for history
- fetch_after = config_or_default(:history_fetch_interval, :timer.minutes(60))
- Process.send_after(self(), {:fetch_history, 1}, fetch_after)
+ {:noreply, state}
+ end
+
+ @impl GenServer
+ def handle_info({:fetch_market_cap_history, day_count}, state) do
+ fetch_market_cap_history(day_count)
+ state = state |> Map.put_new(:price_records, [])
+
+ {:noreply, state}
+ end
+
+ @impl GenServer
+ # Record fetch successful.
+ def handle_info({_ref, {:market_cap_history, {_, _, {:ok, nil}}}}, state) do
+ Process.send(self(), {:fetch_tvl_history, 365}, [])
+ state = state |> Map.put_new(:market_cap_records, [])
+
+ {:noreply, state}
+ end
+
+ @impl GenServer
+ # Record fetch successful.
+ def handle_info({_ref, {:market_cap_history, {_, _, {:ok, market_cap_records}}}}, state) do
+ Process.send(self(), {:fetch_tvl_history, 365}, [])
+ state = state |> Map.put_new(:market_cap_records, market_cap_records)
+
+ {:noreply, state}
+ end
+
+ @impl GenServer
+ def handle_info({:fetch_tvl_history, day_count}, state) do
+ fetch_tvl_history(day_count)
+
+ {:noreply, state}
+ end
+
+ @impl GenServer
+ # Record fetch successful.
+ def handle_info({_ref, {:tvl_history, {_, _, {:ok, nil}}}}, state) do
+ state = state |> Map.put_new(:tvl_records, [])
+ records = compile_records(state)
+ market_cap_history(records, state)
+ end
+
+ @impl GenServer
+ # Record fetch successful.
+ def handle_info({_ref, {:tvl_history, {_, _, {:ok, tvl_records}}}}, state) do
+ state = state |> Map.put_new(:tvl_records, tvl_records)
+ records = compile_records(state)
+ market_cap_history(records, state)
+ end
+
+ # Failed to get records. Try again.
+ @impl GenServer
+ def handle_info({_ref, {:price_history, {day_count, failed_attempts, :error}}}, state) do
+ Logger.warn(fn -> "Failed to fetch price history. Trying again." end)
+
+ fetch_price_history(day_count, failed_attempts + 1)
{:noreply, state}
end
# Failed to get records. Try again.
@impl GenServer
- def handle_info({_ref, {day_count, failed_attempts, :error}}, state) do
- Logger.warn(fn -> "Failed to fetch market history. Trying again." end)
+ def handle_info({_ref, {:market_cap_history, {day_count, failed_attempts, :error}}}, state) do
+ Logger.warn(fn -> "Failed to fetch market cap history. Trying again." end)
- fetch_history(day_count, failed_attempts + 1)
+ fetch_market_cap_history(day_count, failed_attempts + 1)
+
+ {:noreply, state}
+ end
+
+ # Failed to get records. Try again.
+ @impl GenServer
+ def handle_info({_ref, {:tvl_history, {day_count, failed_attempts, :error}}}, state) do
+ Logger.warn(fn -> "Failed to fetch market cap history. Trying again." end)
+
+ fetch_tvl_history(day_count, failed_attempts + 1)
{:noreply, state}
end
@@ -78,36 +141,100 @@ defmodule Explorer.Market.History.Cataloger do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
- @spec base_backoff :: milliseconds()
- defp base_backoff do
- config_or_default(:base_backoff, 100)
+ @spec config_or_default(atom(), term(), term()) :: term()
+ defp config_or_default(key, module, default) do
+ Application.get_env(:explorer, module)[key] || default
end
@spec config_or_default(atom(), term()) :: term()
defp config_or_default(key, default) do
- Application.get_env(:explorer, __MODULE__, [])[key] || default
+ Application.get_env(:explorer, __MODULE__)[key] || default
end
- @spec source() :: module()
- defp source do
- config_or_default(:source, Explorer.Market.History.Source.CryptoCompare)
+ defp market_cap_history(records, state) do
+ Market.bulk_insert_history(records)
+
+ # Schedule next check for history
+ fetch_after = config_or_default(:history_fetch_interval, :timer.minutes(60))
+ Process.send_after(self(), {:fetch_price_history, 1}, fetch_after)
+
+ {:noreply, state}
end
- @spec fetch_history(non_neg_integer(), non_neg_integer()) :: Task.t()
- defp fetch_history(day_count, failed_attempts \\ 0) do
+ @spec source_price() :: module()
+ defp source_price do
+ config_or_default(:price_source, Explorer.ExchangeRates.Source, Explorer.Market.History.Source.Price.CryptoCompare)
+ end
+
+ @spec source_market_cap() :: module()
+ defp source_market_cap do
+ config_or_default(
+ :market_cap_source,
+ Explorer.ExchangeRates.Source,
+ Explorer.Market.History.Source.MarketCap.CoinGecko
+ )
+ end
+
+ @spec source_tvl() :: module()
+ defp source_tvl do
+ config_or_default(
+ :tvl_source,
+ Explorer.ExchangeRates.Source,
+ Explorer.Market.History.Source.TVL.DefiLlama
+ )
+ end
+
+ @spec fetch_price_history(non_neg_integer(), non_neg_integer()) :: Task.t()
+ defp fetch_price_history(day_count, failed_attempts \\ 0) do
+ Task.Supervisor.async_nolink(Explorer.MarketTaskSupervisor, fn ->
+ Process.sleep(HistoryProcess.delay(failed_attempts))
+
+ if failed_attempts < @price_failed_attempts do
+ {:price_history, {day_count, failed_attempts, source_price().fetch_price_history(day_count)}}
+ else
+ {:price_history, {day_count, failed_attempts, {:ok, []}}}
+ end
+ end)
+ end
+
+ @spec fetch_market_cap_history(non_neg_integer()) :: Task.t()
+ defp fetch_market_cap_history(day_count, failed_attempts \\ 0) do
Task.Supervisor.async_nolink(Explorer.MarketTaskSupervisor, fn ->
- Process.sleep(delay(failed_attempts))
- {day_count, failed_attempts, source().fetch_history(day_count)}
+ Process.sleep(HistoryProcess.delay(failed_attempts))
+
+ if failed_attempts < @market_cap_failed_attempts do
+ {:market_cap_history, {day_count, failed_attempts, source_market_cap().fetch_market_cap(day_count)}}
+ else
+ {:market_cap_history, {day_count, failed_attempts, {:ok, nil}}}
+ end
end)
end
- @spec delay(non_neg_integer()) :: milliseconds()
- defp delay(0), do: 0
- defp delay(1), do: base_backoff()
+ @spec fetch_tvl_history(non_neg_integer()) :: Task.t()
+ defp fetch_tvl_history(day_count, failed_attempts \\ 0) do
+ Task.Supervisor.async_nolink(Explorer.MarketTaskSupervisor, fn ->
+ Process.sleep(HistoryProcess.delay(failed_attempts))
- defp delay(failed_attempts) do
- # Simulates 2^n
- multiplier = Enum.reduce(2..failed_attempts, 1, fn _, acc -> 2 * acc end)
- multiplier * base_backoff()
+ if failed_attempts < @tvl_failed_attempts do
+ {:tvl_history, {day_count, failed_attempts, source_tvl().fetch_tvl(day_count)}}
+ else
+ {:tvl_history, {day_count, failed_attempts, {:ok, nil}}}
+ end
+ end)
+ end
+
+ defp compile_records(state) do
+ price_records = state.price_records
+ market_cap_records = state.market_cap_records
+ tvl_records = state.tvl_records
+
+ all_records = price_records ++ market_cap_records ++ tvl_records
+
+ all_records
+ |> Enum.group_by(fn %{date: date} -> date end)
+ |> Map.values()
+ |> Enum.map(fn a ->
+ Enum.reduce(a, %{}, fn x, acc -> Map.merge(x, acc) end)
+ end)
end
end
diff --git a/apps/explorer/lib/explorer/market/history/source/market_cap.ex b/apps/explorer/lib/explorer/market/history/source/market_cap.ex
new file mode 100644
index 000000000000..0a1de2beb7da
--- /dev/null
+++ b/apps/explorer/lib/explorer/market/history/source/market_cap.ex
@@ -0,0 +1,18 @@
+defmodule Explorer.Market.History.Source.MarketCap do
+ @moduledoc """
+ Interface for a source that allows for fetching of market cap history.
+ """
+
+ @typedoc """
+ Record of market values for a specific date.
+ """
+ @type record :: %{
+ date: Date.t(),
+ market_cap: Decimal.t()
+ }
+
+ @doc """
+ Fetch history for a specified amount of days in the past.
+ """
+ @callback fetch_market_cap(previous_days :: non_neg_integer()) :: {:ok, [record()]} | :error
+end
diff --git a/apps/explorer/lib/explorer/market/history/source/market_cap/coin_gecko.ex b/apps/explorer/lib/explorer/market/history/source/market_cap/coin_gecko.ex
new file mode 100644
index 000000000000..fcc3bc262eaf
--- /dev/null
+++ b/apps/explorer/lib/explorer/market/history/source/market_cap/coin_gecko.ex
@@ -0,0 +1,52 @@
+defmodule Explorer.Market.History.Source.MarketCap.CoinGecko do
+ @moduledoc """
+ Adapter for fetching current market from CoinGecko.
+
+ The current market is fetched for the configured coin. You can specify a
+ different coin by changing the targeted coin.
+
+ # In config.exs
+ config :explorer, coin: "POA"
+
+ """
+
+ alias Explorer.ExchangeRates.Source
+ alias Explorer.ExchangeRates.Source.CoinGecko, as: ExchangeRatesSourceCoinGecko
+ alias Explorer.Market.History.Source.MarketCap, as: SourceMarketCap
+ alias Explorer.Market.History.Source.Price.CryptoCompare
+
+ @behaviour SourceMarketCap
+
+ @impl SourceMarketCap
+ def fetch_market_cap(previous_days) do
+ url = ExchangeRatesSourceCoinGecko.history_url(previous_days)
+
+ case Source.http_request(url, ExchangeRatesSourceCoinGecko.headers()) do
+ {:ok, data} ->
+ result =
+ data
+ |> format_data()
+
+ {:ok, result}
+
+ _ ->
+ :error
+ end
+ end
+
+ @spec format_data(term()) :: SourceMarketCap.record() | nil
+ defp format_data(nil), do: nil
+
+ defp format_data(data) do
+ market_caps = data["market_caps"]
+
+ for [date, market_cap] <- market_caps do
+ date = Decimal.to_integer(Decimal.round(Decimal.from_float(date / 1000)))
+
+ %{
+ market_cap: Decimal.new(to_string(market_cap)),
+ date: CryptoCompare.date(date)
+ }
+ end
+ end
+end
diff --git a/apps/explorer/lib/explorer/market/history/source/market_cap/coin_market_cap.ex b/apps/explorer/lib/explorer/market/history/source/market_cap/coin_market_cap.ex
new file mode 100644
index 000000000000..5cf49fd4b85c
--- /dev/null
+++ b/apps/explorer/lib/explorer/market/history/source/market_cap/coin_market_cap.ex
@@ -0,0 +1,56 @@
+defmodule Explorer.Market.History.Source.MarketCap.CoinMarketCap do
+ @moduledoc """
+ Adapter for fetching current market from CoinMarketCap.
+ """
+
+ alias Explorer.ExchangeRates.Source
+ alias Explorer.ExchangeRates.Source.CoinMarketCap, as: ExchangeRatesSourceCoinMarketCap
+ alias Explorer.Market.History.Source.MarketCap, as: SourceMarketCap
+
+ import Source, only: [to_decimal: 1]
+
+ @behaviour SourceMarketCap
+
+ @impl SourceMarketCap
+ def fetch_market_cap(_previous_days) do
+ url = ExchangeRatesSourceCoinMarketCap.source_url()
+
+ if url do
+ case Source.http_request(url, ExchangeRatesSourceCoinMarketCap.headers()) do
+ {:ok, data} ->
+ result =
+ data
+ |> format_data()
+
+ {:ok, result}
+
+ _ ->
+ :error
+ end
+ else
+ :error
+ end
+ end
+
+ @spec format_data(term()) :: SourceMarketCap.record() | nil
+ defp format_data(nil), do: nil
+
+ defp format_data(%{"data" => _} = json_data) do
+ market_data = json_data["data"]
+ token_properties = ExchangeRatesSourceCoinMarketCap.get_token_properties(market_data)
+
+ last_updated =
+ token_properties
+ |> ExchangeRatesSourceCoinMarketCap.get_last_updated()
+ |> DateTime.to_date()
+
+ market_cap_data_usd = ExchangeRatesSourceCoinMarketCap.get_market_cap_data_usd(token_properties)
+
+ [
+ %{
+ market_cap: to_decimal(market_cap_data_usd),
+ date: last_updated
+ }
+ ]
+ end
+end
diff --git a/apps/explorer/lib/explorer/market/history/source.ex b/apps/explorer/lib/explorer/market/history/source/price.ex
similarity index 58%
rename from apps/explorer/lib/explorer/market/history/source.ex
rename to apps/explorer/lib/explorer/market/history/source/price.ex
index af1c96e4bada..07924c1fca3a 100644
--- a/apps/explorer/lib/explorer/market/history/source.ex
+++ b/apps/explorer/lib/explorer/market/history/source/price.ex
@@ -1,6 +1,6 @@
-defmodule Explorer.Market.History.Source do
+defmodule Explorer.Market.History.Source.Price do
@moduledoc """
- Interface for a source that allows for fetching of market history.
+ Interface for a source that allows for fetching of coin price.
"""
@typedoc """
@@ -15,5 +15,5 @@ defmodule Explorer.Market.History.Source do
@doc """
Fetch history for a specified amount of days in the past.
"""
- @callback fetch_history(previous_days :: non_neg_integer()) :: {:ok, [record()]} | :error
+ @callback fetch_price_history(previous_days :: non_neg_integer()) :: {:ok, [record()]} | :error
end
diff --git a/apps/explorer/lib/explorer/market/history/source/price/coin_gecko.ex b/apps/explorer/lib/explorer/market/history/source/price/coin_gecko.ex
new file mode 100644
index 000000000000..ef49d1f19cdf
--- /dev/null
+++ b/apps/explorer/lib/explorer/market/history/source/price/coin_gecko.ex
@@ -0,0 +1,46 @@
+defmodule Explorer.Market.History.Source.Price.CoinGecko do
+ @moduledoc """
+ Adapter for fetching current market from CoinGecko.
+ """
+
+ alias Explorer.ExchangeRates.Source
+ alias Explorer.ExchangeRates.Source.CoinGecko, as: ExchangeRatesSourceCoinGecko
+ alias Explorer.Market.History.Source.Price, as: SourcePrice
+ alias Explorer.Market.History.Source.Price.CryptoCompare
+
+ @behaviour SourcePrice
+
+ @impl SourcePrice
+ def fetch_price_history(previous_days) do
+ url = ExchangeRatesSourceCoinGecko.history_url(previous_days)
+
+ case Source.http_request(url, ExchangeRatesSourceCoinGecko.headers()) do
+ {:ok, data} ->
+ result =
+ data
+ |> format_data()
+
+ {:ok, result}
+
+ _ ->
+ :error
+ end
+ end
+
+ @spec format_data(term()) :: SourcePrice.record() | nil
+ defp format_data(nil), do: nil
+
+ defp format_data(data) do
+ prices = data["prices"]
+
+ for [date, price] <- prices do
+ date = Decimal.to_integer(Decimal.round(Decimal.from_float(date / 1000)))
+
+ %{
+ closing_price: Decimal.new(to_string(price)),
+ date: CryptoCompare.date(date),
+ opening_price: Decimal.new(to_string(price))
+ }
+ end
+ end
+end
diff --git a/apps/explorer/lib/explorer/market/history/source/price/coin_market_cap.ex b/apps/explorer/lib/explorer/market/history/source/price/coin_market_cap.ex
new file mode 100644
index 000000000000..0a8c4bf28dae
--- /dev/null
+++ b/apps/explorer/lib/explorer/market/history/source/price/coin_market_cap.ex
@@ -0,0 +1,55 @@
+defmodule Explorer.Market.History.Source.Price.CoinMarketCap do
+ @moduledoc """
+ Adapter for fetching current market from CoinMarketCap.
+ """
+
+ alias Explorer.ExchangeRates.Source
+ alias Explorer.ExchangeRates.Source.CoinMarketCap, as: ExchangeRatesSourceCoinMarketCap
+ alias Explorer.Market.History.Source.Price, as: SourcePrice
+
+ @behaviour SourcePrice
+
+ @impl SourcePrice
+ def fetch_price_history(_previous_days \\ nil) do
+ url = ExchangeRatesSourceCoinMarketCap.source_url()
+
+ if url do
+ case Source.http_request(url, ExchangeRatesSourceCoinMarketCap.headers()) do
+ {:ok, data} ->
+ result =
+ data
+ |> format_data()
+
+ {:ok, result}
+
+ _ ->
+ :error
+ end
+ else
+ :error
+ end
+ end
+
+ @spec format_data(term()) :: SourcePrice.record() | nil
+ defp format_data(nil), do: nil
+
+ defp format_data(%{"data" => _} = json_data) do
+ market_data = json_data["data"]
+ token_properties = ExchangeRatesSourceCoinMarketCap.get_token_properties(market_data)
+
+ last_updated =
+ token_properties
+ |> ExchangeRatesSourceCoinMarketCap.get_last_updated()
+ |> DateTime.to_date()
+
+ current_price_usd = ExchangeRatesSourceCoinMarketCap.get_current_price(token_properties)
+
+ [
+ %{
+ closing_price: current_price_usd,
+ date: last_updated,
+ opening_price: current_price_usd
+ }
+ ]
+ end
+end
diff --git a/apps/explorer/lib/explorer/market/history/source/crypto_compare.ex b/apps/explorer/lib/explorer/market/history/source/price/crypto_compare.ex
similarity index 85%
rename from apps/explorer/lib/explorer/market/history/source/crypto_compare.ex
rename to apps/explorer/lib/explorer/market/history/source/price/crypto_compare.ex
index 6888d83b2b67..e1b31f03cc2f 100644
--- a/apps/explorer/lib/explorer/market/history/source/crypto_compare.ex
+++ b/apps/explorer/lib/explorer/market/history/source/price/crypto_compare.ex
@@ -1,4 +1,4 @@
-defmodule Explorer.Market.History.Source.CryptoCompare do
+defmodule Explorer.Market.History.Source.Price.CryptoCompare do
@moduledoc """
Adapter for fetching market history from https://cryptocompare.com.
@@ -10,15 +10,15 @@ defmodule Explorer.Market.History.Source.CryptoCompare do
"""
- alias Explorer.Market.History.Source
+ alias Explorer.Market.History.Source.Price, as: SourcePrice
alias HTTPoison.Response
- @behaviour Source
+ @behaviour SourcePrice
@typep unix_timestamp :: non_neg_integer()
- @impl Source
- def fetch_history(previous_days) do
+ @impl SourcePrice
+ def fetch_price_history(previous_days) do
url = history_url(previous_days)
headers = [{"Content-Type", "application/json"}]
@@ -43,13 +43,13 @@ defmodule Explorer.Market.History.Source.CryptoCompare do
end
@spec date(unix_timestamp()) :: Date.t()
- defp date(unix_timestamp) do
+ def date(unix_timestamp) do
unix_timestamp
|> DateTime.from_unix!()
|> DateTime.to_date()
end
- @spec format_data(String.t()) :: [Source.record()]
+ @spec format_data(String.t()) :: [SourcePrice.record()]
defp format_data(data) do
json = Jason.decode!(data)
diff --git a/apps/explorer/lib/explorer/market/history/source/tvl.ex b/apps/explorer/lib/explorer/market/history/source/tvl.ex
new file mode 100644
index 000000000000..19e7da498eb1
--- /dev/null
+++ b/apps/explorer/lib/explorer/market/history/source/tvl.ex
@@ -0,0 +1,18 @@
+defmodule Explorer.Market.History.Source.TVL do
+ @moduledoc """
+ Interface for a source that allows for fetching of TVL history.
+ """
+
+ @typedoc """
+ Record of market values for a specific date.
+ """
+ @type record :: %{
+ date: Date.t(),
+ tvl: Decimal.t()
+ }
+
+ @doc """
+ Fetch history for a specified amount of days in the past.
+ """
+ @callback fetch_tvl(previous_days :: non_neg_integer()) :: {:ok, [record()]} | :error
+end
diff --git a/apps/explorer/lib/explorer/market/history/source/tvl/defillama.ex b/apps/explorer/lib/explorer/market/history/source/tvl/defillama.ex
new file mode 100644
index 000000000000..9fcf29186d5f
--- /dev/null
+++ b/apps/explorer/lib/explorer/market/history/source/tvl/defillama.ex
@@ -0,0 +1,49 @@
+defmodule Explorer.Market.History.Source.TVL.DefiLlama do
+ @moduledoc """
+ Adapter for fetching current market from DefiLlama.
+ """
+
+ alias Explorer.ExchangeRates.Source
+ alias Explorer.ExchangeRates.Source.DefiLlama, as: ExchangeRatesSourceDefiLlama
+ alias Explorer.Market.History.Source.Price.CryptoCompare
+ alias Explorer.Market.History.Source.TVL, as: SourceTVL
+
+ @behaviour SourceTVL
+
+ @impl SourceTVL
+ def fetch_tvl(previous_days) do
+ coin_id = Application.get_env(:explorer, Explorer.ExchangeRates.Source.DefiLlama, [])[:coin_id]
+
+ if coin_id do
+ url =
+ ExchangeRatesSourceDefiLlama.history_url(previous_days) <>
+ "/" <> coin_id
+
+ case Source.http_request(url, ExchangeRatesSourceDefiLlama.headers()) do
+ {:ok, data} ->
+ result =
+ data
+ |> format_data()
+
+ {:ok, result}
+
+ _ ->
+ :error
+ end
+ else
+ {:ok, []}
+ end
+ end
+
+ @spec format_data(term()) :: SourceTVL.record() | nil
+ defp format_data(nil), do: nil
+
+ defp format_data(data) do
+ Enum.map(data, fn %{"date" => date, "tvl" => tvl} ->
+ %{
+ tvl: Decimal.new(to_string(tvl)),
+ date: CryptoCompare.date(date)
+ }
+ end)
+ end
+end
diff --git a/apps/explorer/lib/explorer/market/market.ex b/apps/explorer/lib/explorer/market/market.ex
index d2dead1d2e8b..6de43a1b75b6 100644
--- a/apps/explorer/lib/explorer/market/market.ex
+++ b/apps/explorer/lib/explorer/market/market.ex
@@ -7,13 +7,7 @@ defmodule Explorer.Market do
alias Explorer.Market.{MarketHistory, MarketHistoryCache}
alias Explorer.{ExchangeRates, Repo}
- @doc """
- Get most recent exchange rate for the given symbol.
- """
- @spec get_exchange_rate(String.t()) :: Token.t() | nil
- def get_exchange_rate(symbol) do
- ExchangeRates.lookup(symbol)
- end
+ import Ecto.Query, only: [from: 2]
@doc """
Retrieves the history for the recent specified amount of days.
@@ -25,16 +19,130 @@ defmodule Explorer.Market do
MarketHistoryCache.fetch()
end
+ @doc """
+ Retrieves today's native coin exchange rate from the database.
+ """
+ @spec get_native_coin_exchange_rate_from_db() :: Token.t()
+ def get_native_coin_exchange_rate_from_db do
+ today =
+ case fetch_recent_history() do
+ [today | _the_rest] -> today
+ _ -> nil
+ end
+
+ if today do
+ %Token{
+ usd_value: Map.get(today, :closing_price),
+ market_cap_usd: Map.get(today, :market_cap),
+ tvl_usd: Map.get(today, :tvl),
+ available_supply: nil,
+ total_supply: nil,
+ btc_value: nil,
+ id: nil,
+ last_updated: nil,
+ name: nil,
+ symbol: nil,
+ volume_24h_usd: nil
+ }
+ else
+ Token.null()
+ end
+ end
+
+ @doc """
+ Get most recent exchange rate for the native coin from ETS or from DB.
+ """
+ @spec get_coin_exchange_rate() :: Token.t()
+ def get_coin_exchange_rate do
+ get_native_coin_exchange_rate_from_cache() || get_native_coin_exchange_rate_from_db() || Token.null()
+ end
+
@doc false
def bulk_insert_history(records) do
records_without_zeroes =
records
|> Enum.reject(fn item ->
- Decimal.equal?(item.closing_price, 0) && Decimal.equal?(item.opening_price, 0)
+ Map.has_key?(item, :opening_price) && Map.has_key?(item, :closing_price) &&
+ Decimal.equal?(item.closing_price, 0) &&
+ Decimal.equal?(item.opening_price, 0)
end)
# Enforce MarketHistory ShareLocks order (see docs: sharelocks.md)
|> Enum.sort_by(& &1.date)
- Repo.insert_all(MarketHistory, records_without_zeroes, on_conflict: :nothing, conflict_target: [:date])
+ Repo.insert_all(MarketHistory, records_without_zeroes,
+ on_conflict: market_history_on_conflict(),
+ conflict_target: [:date]
+ )
+ end
+
+ defp market_history_on_conflict do
+ from(
+ market_history in MarketHistory,
+ update: [
+ set: [
+ opening_price:
+ fragment(
+ """
+ CASE WHEN (? IS NULL OR ? = 0) AND EXCLUDED.opening_price IS NOT NULL AND EXCLUDED.opening_price > 0
+ THEN EXCLUDED.opening_price
+ ELSE ?
+ END
+ """,
+ market_history.opening_price,
+ market_history.opening_price,
+ market_history.opening_price
+ ),
+ closing_price:
+ fragment(
+ """
+ CASE WHEN (? IS NULL OR ? = 0) AND EXCLUDED.closing_price IS NOT NULL AND EXCLUDED.closing_price > 0
+ THEN EXCLUDED.closing_price
+ ELSE ?
+ END
+ """,
+ market_history.closing_price,
+ market_history.closing_price,
+ market_history.closing_price
+ ),
+ market_cap:
+ fragment(
+ """
+ CASE WHEN (? IS NULL OR ? = 0) AND EXCLUDED.market_cap IS NOT NULL AND EXCLUDED.market_cap > 0
+ THEN EXCLUDED.market_cap
+ ELSE ?
+ END
+ """,
+ market_history.market_cap,
+ market_history.market_cap,
+ market_history.market_cap
+ ),
+ tvl:
+ fragment(
+ """
+ CASE WHEN (? IS NULL OR ? = 0) AND EXCLUDED.tvl IS NOT NULL AND EXCLUDED.tvl > 0
+ THEN EXCLUDED.tvl
+ ELSE ?
+ END
+ """,
+ market_history.tvl,
+ market_history.tvl,
+ market_history.tvl
+ )
+ ]
+ ],
+ where:
+ is_nil(market_history.tvl) or market_history.tvl == 0 or is_nil(market_history.market_cap) or
+ market_history.market_cap == 0 or is_nil(market_history.opening_price) or
+ market_history.opening_price == 0 or is_nil(market_history.closing_price) or
+ market_history.closing_price == 0
+ )
+ end
+
+ @spec get_native_coin_exchange_rate_from_cache :: Token.t() | nil
+ defp get_native_coin_exchange_rate_from_cache do
+ case ExchangeRates.list() do
+ [native_coin] -> native_coin
+ _ -> nil
+ end
end
end
diff --git a/apps/explorer/lib/explorer/market/market_history.ex b/apps/explorer/lib/explorer/market/market_history.ex
index 10850177b7c5..ea27a083aa48 100644
--- a/apps/explorer/lib/explorer/market/market_history.ex
+++ b/apps/explorer/lib/explorer/market/market_history.ex
@@ -9,6 +9,8 @@ defmodule Explorer.Market.MarketHistory do
field(:closing_price, :decimal)
field(:date, :date)
field(:opening_price, :decimal)
+ field(:market_cap, :decimal)
+ field(:tvl, :decimal)
end
@typedoc """
@@ -17,10 +19,14 @@ defmodule Explorer.Market.MarketHistory do
* `:closing_price` - Closing price in USD.
* `:date` - The date in UTC.
* `:opening_price` - Opening price in USD.
+ * `:market_cap` - Market cap in USD.
+ * `:tvl` - TVL in USD.
"""
@type t :: %__MODULE__{
- closing_price: Decimal.t(),
+ closing_price: Decimal.t() | nil,
date: Date.t(),
- opening_price: Decimal.t()
+ opening_price: Decimal.t() | nil,
+ market_cap: Decimal.t() | nil,
+ tvl: Decimal.t() | nil
}
end
diff --git a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex
new file mode 100644
index 000000000000..59eb1c82403f
--- /dev/null
+++ b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex
@@ -0,0 +1,424 @@
+defmodule Explorer.MicroserviceInterfaces.BENS do
+ @moduledoc """
+ Interface to interact with Blockscout ENS microservice
+ """
+
+ alias Ecto.Association.NotLoaded
+ alias Explorer.Chain
+
+ alias Explorer.Chain.{
+ Address,
+ Address.CurrentTokenBalance,
+ Block,
+ InternalTransaction,
+ Log,
+ TokenTransfer,
+ Transaction,
+ Withdrawal
+ }
+
+ alias Explorer.Utility.Microservice
+ alias HTTPoison.Response
+ require Logger
+
+ @post_timeout :timer.seconds(5)
+ @request_error_msg "Error while sending request to BENS microservice"
+
+ @typep supported_types ::
+ Address.t()
+ | Block.t()
+ | CurrentTokenBalance.t()
+ | InternalTransaction.t()
+ | Log.t()
+ | TokenTransfer.t()
+ | Transaction.t()
+ | Withdrawal.t()
+
+ @doc """
+ Batch request for ENS names via POST {{baseUrl}}/api/v1/:chainId/addresses:batch-resolve-names
+ """
+ @spec ens_names_batch_request([binary()]) :: {:error, :disabled | binary() | Jason.DecodeError.t()} | {:ok, any}
+ def ens_names_batch_request(addresses) do
+ with :ok <- Microservice.check_enabled(__MODULE__) do
+ body = %{
+ addresses: Enum.map(addresses, &to_string/1)
+ }
+
+ http_post_request(batch_resolve_name_url(), body)
+ end
+ end
+
+ @doc """
+ Request for ENS name via GET {{baseUrl}}/api/v1/:chainId/addresses:lookup
+ """
+ @spec address_lookup(binary()) :: {:error, :disabled | binary() | Jason.DecodeError.t()} | {:ok, any}
+ def address_lookup(address) do
+ with :ok <- Microservice.check_enabled(__MODULE__) do
+ query_params = %{
+ "address" => to_string(address),
+ "resolved_to" => true,
+ "owned_by" => false,
+ "only_active" => true,
+ "order" => "ASC"
+ }
+
+ http_get_request(address_lookup_url(), query_params)
+ end
+ end
+
+ @doc """
+ Lookup for ENS domain name via GET {{baseUrl}}/api/v1/:chainId/domains:lookup
+ """
+ @spec ens_domain_lookup(binary()) :: {:error, :disabled | binary() | Jason.DecodeError.t()} | {:ok, any}
+ def ens_domain_lookup(domain) do
+ with :ok <- Microservice.check_enabled(__MODULE__) do
+ query_params = %{
+ "name" => domain,
+ "only_active" => true,
+ "sort" => "registration_date",
+ "order" => "DESC"
+ }
+
+ http_get_request(domain_lookup_url(), query_params)
+ end
+ end
+
+ defp http_post_request(url, body) do
+ headers = [{"Content-Type", "application/json"}]
+
+ case HTTPoison.post(url, Jason.encode!(body), headers, recv_timeout: @post_timeout) do
+ {:ok, %Response{body: body, status_code: 200}} ->
+ Jason.decode(body)
+
+ {_, error} ->
+ old_truncate = Application.get_env(:logger, :truncate)
+ Logger.configure(truncate: :infinity)
+
+ Logger.error(fn ->
+ [
+ "Error while sending request to BENS microservice url: #{url}, body: #{inspect(body, limit: :infinity, printable_limit: :infinity)}: ",
+ inspect(error, limit: :infinity, printable_limit: :infinity)
+ ]
+ end)
+
+ Logger.configure(truncate: old_truncate)
+ {:error, @request_error_msg}
+ end
+ end
+
+ defp http_get_request(url, query_params) do
+ case HTTPoison.get(url, [], params: query_params) do
+ {:ok, %Response{body: body, status_code: 200}} ->
+ Jason.decode(body)
+
+ {_, error} ->
+ old_truncate = Application.get_env(:logger, :truncate)
+ Logger.configure(truncate: :infinity)
+
+ Logger.error(fn ->
+ [
+ "Error while sending request to BENS microservice url: #{url}: ",
+ inspect(error, limit: :infinity, printable_limit: :infinity)
+ ]
+ end)
+
+ Logger.configure(truncate: old_truncate)
+ {:error, @request_error_msg}
+ end
+ end
+
+ @spec enabled?() :: boolean
+ def enabled?, do: Application.get_env(:explorer, __MODULE__)[:enabled]
+
+ defp batch_resolve_name_url do
+ "#{addresses_url()}:batch-resolve-names"
+ end
+
+ defp address_lookup_url do
+ "#{addresses_url()}:lookup"
+ end
+
+ defp domain_lookup_url do
+ "#{domains_url()}:lookup"
+ end
+
+ defp addresses_url do
+ "#{base_url()}/addresses"
+ end
+
+ defp domains_url do
+ "#{base_url()}/domains"
+ end
+
+ defp base_url do
+ chain_id = Application.get_env(:block_scout_web, :chain_id)
+ "#{Microservice.base_url(__MODULE__)}/api/v1/#{chain_id}"
+ end
+
+ @doc """
+ Preload ENS info to list of entities if enabled?()
+ """
+ @spec maybe_preload_ens([supported_types] | supported_types) :: [supported_types] | supported_types
+ def maybe_preload_ens(argument, function \\ &preload_ens_to_list/1) do
+ if enabled?() do
+ function.(argument)
+ else
+ argument
+ end
+ end
+
+ @spec maybe_preload_ens_info_to_search_results(list()) :: list()
+ def maybe_preload_ens_info_to_search_results(list) do
+ maybe_preload_ens(list, &preload_ens_info_to_search_results/1)
+ end
+
+ @spec maybe_preload_ens_to_transaction(Transaction.t()) :: Transaction.t()
+ def maybe_preload_ens_to_transaction(transaction) do
+ maybe_preload_ens(transaction, &preload_ens_to_transaction/1)
+ end
+
+ @spec preload_ens_to_transaction(Transaction.t()) :: Transaction.t()
+ def preload_ens_to_transaction(transaction) do
+ [transaction_with_ens] = preload_ens_to_list([transaction])
+ transaction_with_ens
+ end
+
+ @spec maybe_preload_ens_to_address(Address.t()) :: Address.t()
+ def maybe_preload_ens_to_address(address) do
+ maybe_preload_ens(address, &preload_ens_to_address/1)
+ end
+
+ @spec preload_ens_to_address(Address.t()) :: Address.t()
+ def preload_ens_to_address(address) do
+ [address_with_ens] = preload_ens_to_list([address])
+ address_with_ens
+ end
+
+ @doc """
+ Preload ENS names to list of entities
+ """
+ @spec preload_ens_to_list([supported_types]) :: [supported_types]
+ def preload_ens_to_list(items) do
+ address_hash_strings =
+ Enum.reduce(items, [], fn item, acc ->
+ item_to_address_hash_strings(item) ++ acc
+ end)
+
+ case ens_names_batch_request(address_hash_strings) do
+ {:ok, result} ->
+ put_ens_names(result["names"], items)
+
+ _ ->
+ items
+ end
+ end
+
+ @doc """
+ Preload ENS info to search result, using address_lookup/1
+ """
+ @spec preload_ens_info_to_search_results(list) :: list
+ def preload_ens_info_to_search_results(list) do
+ Enum.map(list, fn
+ %{type: "address", ens_info: ens_info} = search_result when not is_nil(ens_info) ->
+ search_result
+
+ %{type: "address"} = search_result ->
+ ens_info = search_result[:address_hash] |> address_lookup() |> parse_lookup_response()
+ Map.put(search_result, :ens_info, ens_info)
+
+ search_result ->
+ search_result
+ end)
+ end
+
+ @spec ens_domain_name_lookup(binary()) ::
+ nil | %{address_hash: binary(), expiry_date: any(), name: any(), names_count: integer()}
+ def ens_domain_name_lookup(domain) do
+ domain |> ens_domain_lookup() |> parse_lookup_response()
+ end
+
+ defp parse_lookup_response(
+ {:ok,
+ %{
+ "items" =>
+ [
+ %{"name" => name, "expiry_date" => expiry_date, "resolved_address" => %{"hash" => address_hash_string}}
+ | _other
+ ] = items
+ }}
+ ) do
+ {:ok, hash} = Chain.string_to_address_hash(address_hash_string)
+
+ %{
+ name: name,
+ expiry_date: expiry_date,
+ names_count: Enum.count(items),
+ address_hash: Address.checksum(hash)
+ }
+ end
+
+ defp parse_lookup_response(_), do: nil
+
+ defp item_to_address_hash_strings(%Transaction{
+ to_address_hash: nil,
+ created_contract_address_hash: created_contract_address_hash,
+ from_address_hash: from_address_hash
+ }) do
+ [to_string(created_contract_address_hash), to_string(from_address_hash)]
+ end
+
+ defp item_to_address_hash_strings(%Transaction{
+ to_address_hash: to_address_hash,
+ created_contract_address_hash: nil,
+ from_address_hash: from_address_hash,
+ token_transfers: token_transfers
+ }) do
+ token_transfers_addresses =
+ case token_transfers do
+ token_transfers_list when is_list(token_transfers_list) ->
+ List.flatten(Enum.map(token_transfers_list, &item_to_address_hash_strings/1))
+
+ _ ->
+ []
+ end
+
+ [to_string(to_address_hash), to_string(from_address_hash)] ++ token_transfers_addresses
+ end
+
+ defp item_to_address_hash_strings(%TokenTransfer{
+ to_address_hash: to_address_hash,
+ from_address_hash: from_address_hash
+ }) do
+ [to_string(to_address_hash), to_string(from_address_hash)]
+ end
+
+ defp item_to_address_hash_strings(%InternalTransaction{
+ to_address_hash: to_address_hash,
+ from_address_hash: from_address_hash
+ }) do
+ [to_string(to_address_hash), to_string(from_address_hash)]
+ end
+
+ defp item_to_address_hash_strings(%Log{address_hash: address_hash}) do
+ [to_string(address_hash)]
+ end
+
+ defp item_to_address_hash_strings(%Withdrawal{address_hash: address_hash}) do
+ [to_string(address_hash)]
+ end
+
+ defp item_to_address_hash_strings(%Block{miner_hash: miner_hash}) do
+ [to_string(miner_hash)]
+ end
+
+ defp item_to_address_hash_strings(%CurrentTokenBalance{address_hash: address_hash}) do
+ [to_string(address_hash)]
+ end
+
+ defp item_to_address_hash_strings({%Address{} = address, _}) do
+ item_to_address_hash_strings(address)
+ end
+
+ defp item_to_address_hash_strings(%Address{hash: hash}) do
+ [to_string(hash)]
+ end
+
+ defp put_ens_names(names, items) do
+ Enum.map(items, &put_ens_name_to_item(&1, names))
+ end
+
+ defp put_ens_name_to_item(
+ %Transaction{
+ to_address_hash: to_address_hash,
+ created_contract_address_hash: created_contract_address_hash,
+ from_address_hash: from_address_hash
+ } = tx,
+ names
+ ) do
+ token_transfers =
+ case tx.token_transfers do
+ token_transfers_list when is_list(token_transfers_list) ->
+ Enum.map(token_transfers_list, &put_ens_name_to_item(&1, names))
+
+ other ->
+ other
+ end
+
+ %Transaction{
+ tx
+ | to_address: alter_address(tx.to_address, to_address_hash, names),
+ created_contract_address: alter_address(tx.created_contract_address, created_contract_address_hash, names),
+ from_address: alter_address(tx.from_address, from_address_hash, names),
+ token_transfers: token_transfers
+ }
+ end
+
+ defp put_ens_name_to_item(
+ %TokenTransfer{
+ to_address_hash: to_address_hash,
+ from_address_hash: from_address_hash
+ } = tt,
+ names
+ ) do
+ %TokenTransfer{
+ tt
+ | to_address: alter_address(tt.to_address, to_address_hash, names),
+ from_address: alter_address(tt.from_address, from_address_hash, names)
+ }
+ end
+
+ defp put_ens_name_to_item(
+ %InternalTransaction{
+ to_address_hash: to_address_hash,
+ created_contract_address_hash: created_contract_address_hash,
+ from_address_hash: from_address_hash
+ } = tx,
+ names
+ ) do
+ %InternalTransaction{
+ tx
+ | to_address: alter_address(tx.to_address, to_address_hash, names),
+ created_contract_address: alter_address(tx.created_contract_address, created_contract_address_hash, names),
+ from_address: alter_address(tx.from_address, from_address_hash, names)
+ }
+ end
+
+ defp put_ens_name_to_item(%Log{address_hash: address_hash} = log, names) do
+ %Log{log | address: alter_address(log.address, address_hash, names)}
+ end
+
+ defp put_ens_name_to_item(%Withdrawal{address_hash: address_hash} = withdrawal, names) do
+ %Withdrawal{withdrawal | address: alter_address(withdrawal.address, address_hash, names)}
+ end
+
+ defp put_ens_name_to_item(%Block{miner_hash: miner_hash} = block, names) do
+ %Block{block | miner: alter_address(block.miner, miner_hash, names)}
+ end
+
+ defp put_ens_name_to_item(%CurrentTokenBalance{address_hash: address_hash} = current_token_balance, names) do
+ %CurrentTokenBalance{
+ current_token_balance
+ | address: alter_address(current_token_balance.address, address_hash, names)
+ }
+ end
+
+ defp put_ens_name_to_item({%Address{} = address, count}, names) do
+ {put_ens_name_to_item(address, names), count}
+ end
+
+ defp put_ens_name_to_item(%Address{} = address, names) do
+ alter_address(address, address.hash, names)
+ end
+
+ defp alter_address(_, nil, _names) do
+ nil
+ end
+
+ defp alter_address(%NotLoaded{}, address_hash, names) do
+ %{ens_domain_name: names[to_string(address_hash)]}
+ end
+
+ defp alter_address(%Address{} = address, address_hash, names) do
+ %Address{address | ens_domain_name: names[to_string(address_hash)]}
+ end
+end
diff --git a/apps/explorer/lib/explorer/migrator/address_current_token_balance_token_type.ex b/apps/explorer/lib/explorer/migrator/address_current_token_balance_token_type.ex
new file mode 100644
index 000000000000..93226db73fce
--- /dev/null
+++ b/apps/explorer/lib/explorer/migrator/address_current_token_balance_token_type.ex
@@ -0,0 +1,51 @@
+defmodule Explorer.Migrator.AddressCurrentTokenBalanceTokenType do
+ @moduledoc """
+ Fill empty token_type's for address_current_token_balances
+ """
+
+ use Explorer.Migrator.FillingMigration
+
+ import Ecto.Query
+
+ alias Explorer.Chain.Address.CurrentTokenBalance
+ alias Explorer.Chain.Cache.BackgroundMigrations
+ alias Explorer.Migrator.FillingMigration
+ alias Explorer.Repo
+
+ @migration_name "ctb_token_type"
+
+ @impl FillingMigration
+ def migration_name, do: @migration_name
+
+ @impl FillingMigration
+ def last_unprocessed_identifiers do
+ limit = batch_size() * concurrency()
+
+ unprocessed_data_query()
+ |> select([ctb], ctb.id)
+ |> limit(^limit)
+ |> Repo.all(timeout: :infinity)
+ end
+
+ @impl FillingMigration
+ def unprocessed_data_query do
+ from(ctb in CurrentTokenBalance, where: is_nil(ctb.token_type))
+ end
+
+ @impl FillingMigration
+ def update_batch(token_balance_ids) do
+ query =
+ from(current_token_balance in CurrentTokenBalance,
+ join: token in assoc(current_token_balance, :token),
+ where: current_token_balance.id in ^token_balance_ids,
+ update: [set: [token_type: token.type]]
+ )
+
+ Repo.update_all(query, [], timeout: :infinity)
+ end
+
+ @impl FillingMigration
+ def update_cache do
+ BackgroundMigrations.set_ctb_token_type_finished(true)
+ end
+end
diff --git a/apps/explorer/lib/explorer/migrator/address_token_balance_token_type.ex b/apps/explorer/lib/explorer/migrator/address_token_balance_token_type.ex
new file mode 100644
index 000000000000..9427db73ed60
--- /dev/null
+++ b/apps/explorer/lib/explorer/migrator/address_token_balance_token_type.ex
@@ -0,0 +1,51 @@
+defmodule Explorer.Migrator.AddressTokenBalanceTokenType do
+ @moduledoc """
+ Fill empty token_type's for address_token_balances
+ """
+
+ use Explorer.Migrator.FillingMigration
+
+ import Ecto.Query
+
+ alias Explorer.Chain.Address.TokenBalance
+ alias Explorer.Chain.Cache.BackgroundMigrations
+ alias Explorer.Migrator.FillingMigration
+ alias Explorer.Repo
+
+ @migration_name "tb_token_type"
+
+ @impl FillingMigration
+ def migration_name, do: @migration_name
+
+ @impl FillingMigration
+ def last_unprocessed_identifiers do
+ limit = batch_size() * concurrency()
+
+ unprocessed_data_query()
+ |> select([tb], tb.id)
+ |> limit(^limit)
+ |> Repo.all(timeout: :infinity)
+ end
+
+ @impl FillingMigration
+ def unprocessed_data_query do
+ from(tb in TokenBalance, where: is_nil(tb.token_type))
+ end
+
+ @impl FillingMigration
+ def update_batch(token_balance_ids) do
+ query =
+ from(token_balance in TokenBalance,
+ join: token in assoc(token_balance, :token),
+ where: token_balance.id in ^token_balance_ids,
+ update: [set: [token_type: token.type]]
+ )
+
+ Repo.update_all(query, [], timeout: :infinity)
+ end
+
+ @impl FillingMigration
+ def update_cache do
+ BackgroundMigrations.set_tb_token_type_finished(true)
+ end
+end
diff --git a/apps/explorer/lib/explorer/migrator/filling_migration.ex b/apps/explorer/lib/explorer/migrator/filling_migration.ex
new file mode 100644
index 000000000000..507dfcb6e5f7
--- /dev/null
+++ b/apps/explorer/lib/explorer/migrator/filling_migration.ex
@@ -0,0 +1,84 @@
+defmodule Explorer.Migrator.FillingMigration do
+ @moduledoc """
+ Template for creating migrations that fills some fields in existing entities
+ """
+
+ @callback migration_name :: String.t()
+ @callback unprocessed_data_query :: Ecto.Query.t()
+ @callback last_unprocessed_identifiers :: [any()]
+ @callback update_batch([any()]) :: any()
+ @callback update_cache :: any()
+
+ defmacro __using__(_opts) do
+ quote do
+ @behaviour Explorer.Migrator.FillingMigration
+
+ use GenServer, restart: :transient
+
+ import Ecto.Query
+
+ alias Explorer.Migrator.MigrationStatus
+ alias Explorer.Repo
+
+ @default_batch_size 500
+
+ def start_link(_) do
+ GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
+ end
+
+ def migration_finished? do
+ MigrationStatus.get_status(migration_name()) == "completed"
+ end
+
+ @impl true
+ def init(_) do
+ case MigrationStatus.get_status(migration_name()) do
+ "completed" ->
+ update_cache()
+ :ignore
+
+ _ ->
+ MigrationStatus.set_status(migration_name(), "started")
+ schedule_batch_migration()
+ {:ok, %{}}
+ end
+ end
+
+ @impl true
+ def handle_info(:migrate_batch, state) do
+ case last_unprocessed_identifiers() do
+ [] ->
+ update_cache()
+ MigrationStatus.set_status(migration_name(), "completed")
+ {:stop, :normal, state}
+
+ hashes ->
+ hashes
+ |> Enum.chunk_every(batch_size())
+ |> Enum.map(&run_task/1)
+ |> Task.await_many(:infinity)
+
+ schedule_batch_migration()
+
+ {:noreply, state}
+ end
+ end
+
+ defp run_task(batch), do: Task.async(fn -> update_batch(batch) end)
+
+ defp schedule_batch_migration do
+ Process.send(self(), :migrate_batch, [])
+ end
+
+ defp batch_size do
+ Application.get_env(:explorer, __MODULE__)[:batch_size] || @default_batch_size
+ end
+
+ defp concurrency do
+ default = 4 * System.schedulers_online()
+
+ Application.get_env(:explorer, __MODULE__)[:concurrency] || default
+ end
+ end
+ end
+end
diff --git a/apps/explorer/lib/explorer/migrator/migration_status.ex b/apps/explorer/lib/explorer/migrator/migration_status.ex
new file mode 100644
index 000000000000..01a7bbd54084
--- /dev/null
+++ b/apps/explorer/lib/explorer/migrator/migration_status.ex
@@ -0,0 +1,32 @@
+defmodule Explorer.Migrator.MigrationStatus do
+ @moduledoc """
+ Module is responsible for keeping the current status of background migrations.
+ """
+ use Explorer.Schema
+
+ alias Explorer.Repo
+
+ @primary_key false
+ schema "migrations_status" do
+ field(:migration_name, :string)
+ # ["started", "completed"]
+ field(:status, :string)
+
+ timestamps()
+ end
+
+ @doc false
+ def changeset(migration_status \\ %__MODULE__{}, params) do
+ cast(migration_status, params, [:migration_name, :status])
+ end
+
+ def get_status(migration_name) do
+ Repo.one(from(ms in __MODULE__, where: ms.migration_name == ^migration_name, select: ms.status))
+ end
+
+ def set_status(migration_name, status) do
+ %{migration_name: migration_name, status: status}
+ |> changeset()
+ |> Repo.insert(on_conflict: {:replace_all_except, [:inserted_at]}, conflict_target: :migration_name)
+ end
+end
diff --git a/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex b/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex
new file mode 100644
index 000000000000..fe66548aba14
--- /dev/null
+++ b/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex
@@ -0,0 +1,53 @@
+defmodule Explorer.Migrator.TransactionsDenormalization do
+ @moduledoc """
+ Migrates all transactions to have set block_consensus and block_timestamp
+ """
+
+ use Explorer.Migrator.FillingMigration
+
+ import Ecto.Query
+
+ alias Explorer.Chain.Cache.BackgroundMigrations
+ alias Explorer.Chain.Transaction
+ alias Explorer.Migrator.FillingMigration
+ alias Explorer.Repo
+
+ @migration_name "denormalization"
+
+ @impl FillingMigration
+ def migration_name, do: @migration_name
+
+ @impl FillingMigration
+ def last_unprocessed_identifiers do
+ limit = batch_size() * concurrency()
+
+ unprocessed_data_query()
+ |> select([t], t.hash)
+ |> limit(^limit)
+ |> Repo.all(timeout: :infinity)
+ end
+
+ @impl FillingMigration
+ def unprocessed_data_query do
+ from(t in Transaction,
+ where: not is_nil(t.block_hash) and (is_nil(t.block_consensus) or is_nil(t.block_timestamp))
+ )
+ end
+
+ @impl FillingMigration
+ def update_batch(transaction_hashes) do
+ query =
+ from(transaction in Transaction,
+ join: block in assoc(transaction, :block),
+ where: transaction.hash in ^transaction_hashes,
+ update: [set: [block_consensus: block.consensus, block_timestamp: block.timestamp]]
+ )
+
+ Repo.update_all(query, [], timeout: :infinity)
+ end
+
+ @impl FillingMigration
+ def update_cache do
+ BackgroundMigrations.set_denormalization_finished(true)
+ end
+end
diff --git a/apps/explorer/lib/explorer/repo.ex b/apps/explorer/lib/explorer/repo.ex
index c64db8b97d21..c4d0c8f3f489 100644
--- a/apps/explorer/lib/explorer/repo.ex
+++ b/apps/explorer/lib/explorer/repo.ex
@@ -178,4 +178,100 @@ defmodule Explorer.Repo do
{:ok, Keyword.put(opts, :url, db_url)}
end
end
+
+ defmodule PolygonEdge do
+ use Ecto.Repo,
+ otp_app: :explorer,
+ adapter: Ecto.Adapters.Postgres
+
+ def init(_, opts) do
+ db_url = Application.get_env(:explorer, Explorer.Repo.PolygonEdge)[:url]
+ repo_conf = Application.get_env(:explorer, Explorer.Repo.PolygonEdge)
+
+ merged =
+ %{url: db_url}
+ |> ConfigHelper.get_db_config()
+ |> Keyword.merge(repo_conf, fn
+ _key, v1, nil -> v1
+ _key, nil, v2 -> v2
+ _, _, v2 -> v2
+ end)
+
+ Application.put_env(:explorer, Explorer.Repo.PolygonEdge, merged)
+
+ {:ok, Keyword.put(opts, :url, db_url)}
+ end
+ end
+
+ defmodule PolygonZkevm do
+ use Ecto.Repo,
+ otp_app: :explorer,
+ adapter: Ecto.Adapters.Postgres
+
+ def init(_, opts) do
+ db_url = Application.get_env(:explorer, __MODULE__)[:url]
+ repo_conf = Application.get_env(:explorer, __MODULE__)
+
+ merged =
+ %{url: db_url}
+ |> ConfigHelper.get_db_config()
+ |> Keyword.merge(repo_conf, fn
+ _key, v1, nil -> v1
+ _key, nil, v2 -> v2
+ _, _, v2 -> v2
+ end)
+
+ Application.put_env(:explorer, __MODULE__, merged)
+
+ {:ok, Keyword.put(opts, :url, db_url)}
+ end
+ end
+
+ defmodule RSK do
+ use Ecto.Repo,
+ otp_app: :explorer,
+ adapter: Ecto.Adapters.Postgres
+
+ def init(_, opts) do
+ db_url = Application.get_env(:explorer, __MODULE__)[:url]
+ repo_conf = Application.get_env(:explorer, __MODULE__)
+
+ merged =
+ %{url: db_url}
+ |> ConfigHelper.get_db_config()
+ |> Keyword.merge(repo_conf, fn
+ _key, v1, nil -> v1
+ _key, nil, v2 -> v2
+ _, _, v2 -> v2
+ end)
+
+ Application.put_env(:explorer, __MODULE__, merged)
+
+ {:ok, Keyword.put(opts, :url, db_url)}
+ end
+ end
+
+ defmodule Suave do
+ use Ecto.Repo,
+ otp_app: :explorer,
+ adapter: Ecto.Adapters.Postgres
+
+ def init(_, opts) do
+ db_url = Application.get_env(:explorer, __MODULE__)[:url]
+ repo_conf = Application.get_env(:explorer, __MODULE__)
+
+ merged =
+ %{url: db_url}
+ |> ConfigHelper.get_db_config()
+ |> Keyword.merge(repo_conf, fn
+ _key, v1, nil -> v1
+ _key, nil, v2 -> v2
+ _, _, v2 -> v2
+ end)
+
+ Application.put_env(:explorer, __MODULE__, merged)
+
+ {:ok, Keyword.put(opts, :url, db_url)}
+ end
+ end
end
diff --git a/apps/explorer/lib/explorer/repo/config_helper.ex b/apps/explorer/lib/explorer/repo/config_helper.ex
index dd13d83a2cb4..abfe46c958ef 100644
--- a/apps/explorer/lib/explorer/repo/config_helper.ex
+++ b/apps/explorer/lib/explorer/repo/config_helper.ex
@@ -28,6 +28,8 @@ defmodule Explorer.Repo.ConfigHelper do
def get_account_db_url, do: System.get_env("ACCOUNT_DATABASE_URL") || System.get_env("DATABASE_URL")
+ def get_suave_db_url, do: System.get_env("SUAVE_DATABASE_URL") || System.get_env("DATABASE_URL")
+
def get_api_db_url, do: System.get_env("DATABASE_READ_ONLY_API_URL") || System.get_env("DATABASE_URL")
def ssl_enabled?, do: String.equivalent?(System.get_env("ECTO_USE_SSL") || "true", "true")
@@ -36,7 +38,7 @@ defmodule Explorer.Repo.ConfigHelper do
# sobelow_skip ["DOS.StringToAtom"]
defp extract_parameters(database_url) do
- ~r/\w*:\/\/(?\w+):(?[a-zA-Z0-9-*#!%^&$_]*)?@(?(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])):(?\d+)\/(?[a-zA-Z0-9_-]*)/
+ ~r/\w*:\/\/(?[a-zA-Z0-9_-]*):(?[a-zA-Z0-9-*#!%^&$_.]*)?@(?(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])):(?\d+)\/(?[a-zA-Z0-9_-]*)/
|> Regex.named_captures(database_url)
|> Keyword.new(fn {k, v} -> {String.to_atom(k), v} end)
|> Keyword.put(:url, database_url)
@@ -58,6 +60,17 @@ defmodule Explorer.Repo.ConfigHelper do
path_from_env(path)
end
+ @doc """
+ Defines http port of the application
+ """
+ @spec get_port() :: non_neg_integer()
+ def get_port do
+ case System.get_env("PORT") && Integer.parse(System.get_env("PORT")) do
+ {port, _} -> port
+ _ -> 4000
+ end
+ end
+
defp path_from_env(path_env_var) do
if String.ends_with?(path_env_var, "/") do
path_env_var
diff --git a/apps/explorer/lib/explorer/smart_contract/compiler_version.ex b/apps/explorer/lib/explorer/smart_contract/compiler_version.ex
index e3d2bcde8a07..3a0e898ff5e2 100644
--- a/apps/explorer/lib/explorer/smart_contract/compiler_version.ex
+++ b/apps/explorer/lib/explorer/smart_contract/compiler_version.ex
@@ -31,36 +31,25 @@ defmodule Explorer.SmartContract.CompilerVersion do
end
defp fetch_solc_versions do
- if RustVerifierInterface.enabled?() do
- RustVerifierInterface.get_versions_list()
- else
- headers = [{"Content-Type", "application/json"}]
-
- case HTTPoison.get(source_url(:solc), headers) do
- {:ok, %{status_code: 200, body: body}} ->
- {:ok, format_data(body, :solc)}
-
- {:ok, %{status_code: _status_code, body: body}} ->
- {:error, decode_json(body)["error"]}
-
- {:error, %{reason: reason}} ->
- {:error, reason}
- end
- end
+ fetch_compiler_versions(&RustVerifierInterface.get_versions_list/0, :solc)
end
defp fetch_vyper_versions do
+ fetch_compiler_versions(&RustVerifierInterface.vyper_get_versions_list/0, :vyper)
+ end
+
+ defp fetch_compiler_versions(compiler_list_fn, compiler_type) do
if RustVerifierInterface.enabled?() do
- RustVerifierInterface.vyper_get_versions_list()
+ compiler_list_fn.()
else
headers = [{"Content-Type", "application/json"}]
- case HTTPoison.get(source_url(:vyper), headers) do
+ case HTTPoison.get(source_url(compiler_type), headers) do
{:ok, %{status_code: 200, body: body}} ->
- {:ok, format_data(body, :vyper)}
+ {:ok, format_data(body, compiler_type)}
{:ok, %{status_code: _status_code, body: body}} ->
- {:error, decode_json(body)["error"]}
+ {:error, Helper.decode_json(body)["error"]}
{:error, %{reason: reason}} ->
{:error, reason}
@@ -140,10 +129,6 @@ defmodule Explorer.SmartContract.CompilerVersion do
end
end
- defp decode_json(json) do
- Jason.decode!(json)
- end
-
@spec source_url(:solc | :vyper) :: String.t()
defp source_url(compiler) do
case compiler do
diff --git a/apps/explorer/lib/explorer/smart_contract/eth_bytecode_db_interface.ex b/apps/explorer/lib/explorer/smart_contract/eth_bytecode_db_interface.ex
index 51656f766883..82b83c053c7d 100644
--- a/apps/explorer/lib/explorer/smart_contract/eth_bytecode_db_interface.ex
+++ b/apps/explorer/lib/explorer/smart_contract/eth_bytecode_db_interface.ex
@@ -3,10 +3,41 @@ defmodule Explorer.SmartContract.EthBytecodeDBInterface do
Adapter for interaction with https://github.com/blockscout/blockscout-rs/tree/main/eth-bytecode-db
"""
- def search_contract(%{"bytecode" => _, "bytecodeType" => _} = body) do
+ def search_contract(%{"bytecode" => _, "bytecodeType" => _} = body, address_hash) do
+ if chain_id = Application.get_env(:block_scout_web, :chain_id) do
+ http_post_request(
+ bytecode_search_all_sources_url(),
+ Map.merge(body, %{
+ "chain" => to_string(chain_id),
+ "address" => to_string(address_hash)
+ })
+ )
+ else
+ http_post_request(bytecode_search_sources_url(), body)
+ end
+ end
+
+ @doc """
+ Function to search smart contracts in eth-bytecode-db, similar to `search_contract/2` but
+ this function uses only `/api/v2/bytecodes/sources:search` method
+ """
+ @spec search_contract_in_eth_bytecode_internal_db(map()) :: {:error, any} | {:ok, any}
+ def search_contract_in_eth_bytecode_internal_db(%{"bytecode" => _, "bytecodeType" => _} = body) do
http_post_request(bytecode_search_sources_url(), body)
end
+ def process_verifier_response(%{"sourcifySources" => [src | _]}) do
+ {:ok, Map.put(src, "sourcify?", true)}
+ end
+
+ def process_verifier_response(%{"ethBytecodeDbSources" => [src | _]}) do
+ {:ok, src}
+ end
+
+ def process_verifier_response(%{"ethBytecodeDbSources" => [], "sourcifySources" => []}) do
+ {:error, :no_matched_sources}
+ end
+
def process_verifier_response(%{"sources" => [src | _]}) do
{:ok, src}
end
@@ -15,7 +46,23 @@ defmodule Explorer.SmartContract.EthBytecodeDBInterface do
{:ok, nil}
end
- def bytecode_search_sources_url, do: "#{base_api_url()}" <> "/bytecodes/sources:search"
+ def bytecode_search_sources_url do
+ # workaround because of https://github.com/PSPDFKit-labs/bypass/issues/122
+ if Mix.env() == :test do
+ "#{base_api_url()}" <> "/bytecodes/sources_search"
+ else
+ "#{base_api_url()}" <> "/bytecodes/sources:search"
+ end
+ end
+
+ def bytecode_search_all_sources_url do
+ # workaround because of https://github.com/PSPDFKit-labs/bypass/issues/122
+ if Mix.env() == :test do
+ "#{base_api_url()}" <> "/bytecodes/sources_search_all"
+ else
+ "#{base_api_url()}" <> "/bytecodes/sources:search-all"
+ end
+ end
use Explorer.SmartContract.RustVerifierInterfaceBehaviour
end
diff --git a/apps/explorer/lib/explorer/smart_contract/helper.ex b/apps/explorer/lib/explorer/smart_contract/helper.ex
index 697327a75b9b..40ffcaa69866 100644
--- a/apps/explorer/lib/explorer/smart_contract/helper.ex
+++ b/apps/explorer/lib/explorer/smart_contract/helper.ex
@@ -4,6 +4,7 @@ defmodule Explorer.SmartContract.Helper do
"""
alias Explorer.Chain
+ alias Explorer.Chain.{Hash, SmartContract}
alias Phoenix.HTML
def queriable_method?(method) do
@@ -22,12 +23,10 @@ defmodule Explorer.SmartContract.Helper do
@spec read_with_wallet_method?(%{}) :: true | false
def read_with_wallet_method?(function),
do:
- !error?(function) && !event?(function) && !constructor?(function) && nonpayable?(function) &&
+ !error?(function) && !event?(function) && !constructor?(function) &&
!empty_outputs?(function)
- def empty_inputs?(function), do: function["inputs"] == []
-
- def empty_outputs?(function), do: function["outputs"] == []
+ def empty_outputs?(function), do: is_nil(function["outputs"]) || function["outputs"] == []
def payable?(function), do: function["stateMutability"] == "payable" || function["payable"]
@@ -117,11 +116,17 @@ defmodule Explorer.SmartContract.Helper do
end
def cast_libraries(map) do
- map
- |> Map.values()
- |> Enum.reduce(%{}, fn map, acc -> Map.merge(acc, map) end)
+ map |> Map.values() |> List.first() |> cast_libraries(map)
end
+ def cast_libraries(value, map) when is_map(value),
+ do:
+ map
+ |> Map.values()
+ |> Enum.reduce(%{}, fn map, acc -> Map.merge(acc, map) end)
+
+ def cast_libraries(_value, map), do: map
+
def contract_creation_input(address_hash) do
case Chain.smart_contract_creation_tx_bytecode(address_hash) do
%{init: init, created_contract_code: _created_contract_code} ->
@@ -131,4 +136,61 @@ defmodule Explorer.SmartContract.Helper do
nil
end
end
+
+ @doc """
+ Returns a tuple: `{creation_bytecode, deployed_bytecode, metadata}` where `metadata` is a map:
+ {
+ "blockNumber": "string",
+ "chainId": "string",
+ "contractAddress": "string",
+ "creationCode": "string",
+ "deployer": "string",
+ "runtimeCode": "string",
+ "transactionHash": "string",
+ "transactionIndex": "string"
+ }
+
+ Metadata will be sent to a verifier microservice
+ """
+ @spec fetch_data_for_verification(binary() | Hash.t()) :: {binary() | nil, binary(), map()}
+ def fetch_data_for_verification(address_hash) do
+ deployed_bytecode = Chain.smart_contract_bytecode(address_hash)
+
+ metadata = %{
+ "contractAddress" => to_string(address_hash),
+ "runtimeCode" => to_string(deployed_bytecode),
+ "chainId" => Application.get_env(:block_scout_web, :chain_id)
+ }
+
+ case SmartContract.creation_tx_with_bytecode(address_hash) do
+ %{init: init, tx: tx} ->
+ {init, deployed_bytecode, tx |> tx_to_metadata(init) |> Map.merge(metadata)}
+
+ %{init: init, internal_tx: internal_tx} ->
+ {init, deployed_bytecode, internal_tx |> internal_tx_to_metadata(init) |> Map.merge(metadata)}
+
+ _ ->
+ {nil, deployed_bytecode, metadata}
+ end
+ end
+
+ defp tx_to_metadata(tx, init) do
+ %{
+ "blockNumber" => to_string(tx.block_number),
+ "transactionHash" => to_string(tx.hash),
+ "transactionIndex" => to_string(tx.index),
+ "deployer" => to_string(tx.from_address_hash),
+ "creationCode" => to_string(init)
+ }
+ end
+
+ defp internal_tx_to_metadata(internal_tx, init) do
+ %{
+ "blockNumber" => to_string(internal_tx.block_number),
+ "transactionHash" => to_string(internal_tx.transaction_hash),
+ "transactionIndex" => to_string(internal_tx.transaction_index),
+ "deployer" => to_string(internal_tx.from_address_hash),
+ "creationCode" => to_string(init)
+ }
+ end
end
diff --git a/apps/explorer/lib/explorer/smart_contract/reader.ex b/apps/explorer/lib/explorer/smart_contract/reader.ex
index 6f0d3fa93812..cdfd2ca51c53 100644
--- a/apps/explorer/lib/explorer/smart_contract/reader.ex
+++ b/apps/explorer/lib/explorer/smart_contract/reader.ex
@@ -7,8 +7,8 @@ defmodule Explorer.SmartContract.Reader do
"""
alias EthereumJSONRPC.{Contract, Encoder}
- alias Explorer.Chain
alias Explorer.Chain.{Hash, SmartContract}
+ alias Explorer.Chain.SmartContract.Proxy
alias Explorer.SmartContract.Helper
@typedoc """
@@ -92,7 +92,7 @@ defmodule Explorer.SmartContract.Reader do
defp prepare_abi(nil, address_hash) do
address_hash
- |> Chain.address_hash_to_smart_contract()
+ |> SmartContract.address_hash_to_smart_contract()
|> Map.get(:abi)
end
@@ -222,7 +222,7 @@ defmodule Explorer.SmartContract.Reader do
def read_only_functions(nil, _, _), do: []
def read_only_functions_proxy(contract_address_hash, implementation_address_hash_string, from, options \\ []) do
- implementation_abi = Chain.get_implementation_abi(implementation_address_hash_string, options)
+ implementation_abi = SmartContract.get_smart_contract_abi(implementation_address_hash_string, options)
case implementation_abi do
nil ->
@@ -238,7 +238,7 @@ defmodule Explorer.SmartContract.Reader do
"""
@spec read_functions_required_wallet_proxy(String.t()) :: [%{}]
def read_functions_required_wallet_proxy(implementation_address_hash_string) do
- implementation_abi = Chain.get_implementation_abi(implementation_address_hash_string)
+ implementation_abi = SmartContract.get_smart_contract_abi(implementation_address_hash_string)
case implementation_abi do
nil ->
@@ -270,7 +270,7 @@ defmodule Explorer.SmartContract.Reader do
abi_with_method_id
|> Enum.filter(&Helper.queriable_method?(&1))
- |> Enum.map(&fetch_current_value_from_blockchain(&1, abi_with_method_id, contract_address_hash, false, from))
+ |> Enum.map(&fetch_current_value_from_blockchain(&1, abi_with_method_id, contract_address_hash, false, [], from))
end
def read_only_functions_from_abi_with_sender(_, _, _), do: []
@@ -332,21 +332,34 @@ defmodule Explorer.SmartContract.Reader do
"tuple[#{tuple_types}]"
end
- def fetch_current_value_from_blockchain(function, abi, contract_address_hash, leave_error_as_map, from \\ nil) do
+ def fetch_current_value_from_blockchain(
+ function,
+ abi,
+ contract_address_hash,
+ leave_error_as_map,
+ options,
+ from \\ nil
+ ) do
case function do
%{"inputs" => []} ->
method_id = function["method_id"]
args = function["inputs"]
- outputs = function["outputs"]
- values =
- contract_address_hash
- |> query_verified_contract(%{method_id => normalize_args(args)}, from, leave_error_as_map, abi)
- |> link_outputs_and_values(outputs, method_id)
+ %{output: outputs, names: names} =
+ query_function_with_names(
+ contract_address_hash,
+ %{method_id: method_id, args: args},
+ :regular,
+ from,
+ abi,
+ leave_error_as_map,
+ options
+ )
function
- |> Map.replace!("outputs", values)
+ |> Map.replace!("outputs", outputs)
|> Map.put("abi_outputs", Map.get(function, "outputs", []))
+ |> Map.put("names", names)
_ ->
function
@@ -364,9 +377,10 @@ defmodule Explorer.SmartContract.Reader do
%{method_id: String.t(), args: [term()] | nil},
:regular | :proxy,
String.t() | nil,
- [api?]
+ [],
+ boolean()
) :: %{:names => [any()], :output => [%{}]}
- def query_function_with_names(contract_address_hash, params, type, from, abi, options \\ [])
+ def query_function_with_names(contract_address_hash, params, type, from, abi, leave_error_as_map, options \\ [])
def query_function_with_names(
contract_address_hash,
@@ -374,6 +388,7 @@ defmodule Explorer.SmartContract.Reader do
:regular,
from,
abi,
+ leave_error_as_map,
_options
) do
outputs =
@@ -382,7 +397,7 @@ defmodule Explorer.SmartContract.Reader do
method_id,
args || [],
from,
- true,
+ leave_error_as_map,
abi
)
@@ -390,7 +405,15 @@ defmodule Explorer.SmartContract.Reader do
%{output: outputs, names: names}
end
- def query_function_with_names(contract_address_hash, %{method_id: method_id, args: args}, :proxy, from, _abi, options) do
+ def query_function_with_names(
+ contract_address_hash,
+ %{method_id: method_id, args: args},
+ :proxy,
+ from,
+ _abi,
+ leave_error_as_map,
+ options
+ ) do
abi = get_abi(contract_address_hash, :proxy, options)
outputs =
@@ -399,7 +422,7 @@ defmodule Explorer.SmartContract.Reader do
method_id,
args || [],
from,
- true,
+ leave_error_as_map,
abi
)
@@ -549,10 +572,10 @@ defmodule Explorer.SmartContract.Reader do
end
defp get_abi(contract_address_hash, type, options) do
- contract = Chain.address_hash_to_smart_contract(contract_address_hash, options)
+ contract = SmartContract.address_hash_to_smart_contract(contract_address_hash, options)
if type == :proxy do
- Chain.get_implementation_abi_from_proxy(contract, options)
+ Proxy.get_implementation_abi_from_proxy(contract, options)
else
contract.abi
end
@@ -731,6 +754,27 @@ defmodule Explorer.SmartContract.Reader do
Map.put_new(output, "value", Encoder.unescape(value))
end
+ defp new_value(%{"type" => "tuple" <> _types = type} = output, values, index) do
+ value = Enum.at(values, index)
+
+ result =
+ if String.ends_with?(type, "[]") do
+ value
+ |> Enum.map(fn tuple -> new_value(%{"type" => String.slice(type, 0..-3)}, [tuple], 0) end)
+ |> flat_arrays_map()
+ else
+ value
+ |> zip_tuple_values_with_types(type)
+ |> Enum.map(fn {type, part_value} ->
+ new_value(%{"type" => type}, [part_value], 0)
+ end)
+ |> flat_arrays_map()
+ |> List.to_tuple()
+ end
+
+ Map.put_new(output, "value", result)
+ end
+
defp new_value(output, [value], _index) do
Map.put_new(output, "value", value)
end
@@ -739,6 +783,68 @@ defmodule Explorer.SmartContract.Reader do
Map.put_new(output, "value", Enum.at(values, index))
end
+ defp flat_arrays_map(%{"value" => value}) do
+ flat_arrays_map(value)
+ end
+
+ defp flat_arrays_map(value) when is_list(value) do
+ Enum.map(value, &flat_arrays_map/1)
+ end
+
+ defp flat_arrays_map(value) when is_tuple(value) do
+ value
+ |> Tuple.to_list()
+ |> flat_arrays_map()
+ |> List.to_tuple()
+ end
+
+ defp flat_arrays_map(value) do
+ value
+ end
+
+ @spec zip_tuple_values_with_types(tuple, binary) :: [{binary, any}]
+ def zip_tuple_values_with_types(value, type) do
+ types_string =
+ type
+ |> String.slice(6..-2)
+
+ types =
+ if String.trim(types_string) == "" do
+ []
+ else
+ types_string
+ |> String.graphemes()
+ end
+
+ tuple_types =
+ types
+ |> Enum.reduce(
+ {[""], 0},
+ fn
+ ",", {types_acc, 0} ->
+ {["" | types_acc], 0}
+
+ char, {[acc | types_acc], bracket_stack} ->
+ new_bracket_stack =
+ case char do
+ "[" -> bracket_stack + 1
+ "]" -> bracket_stack - 1
+ _ -> bracket_stack
+ end
+
+ {[acc <> char | types_acc], new_bracket_stack}
+ end
+ )
+ |> elem(0)
+ |> Enum.reverse()
+
+ values_list =
+ value
+ |> Tuple.to_list()
+
+ Enum.zip(tuple_types, values_list)
+ end
+
@spec bytes_to_string(<<_::_*8>>) :: String.t()
defp bytes_to_string(value) do
if value do
diff --git a/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex b/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex
index 4665de0d1cdb..2c5629d219c0 100644
--- a/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex
+++ b/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex
@@ -3,12 +3,13 @@ defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do
This behaviour module was created in order to add possibility to extend the functionality of RustVerifierInterface
"""
defmacro __using__(_) do
+ # credo:disable-for-next-line
quote([]) do
- alias Explorer.Utility.RustService
+ alias Explorer.Utility.Microservice
alias HTTPoison.Response
require Logger
- @post_timeout :timer.seconds(120)
+ @post_timeout :timer.minutes(5)
@request_error_msg "Error while sending request to verification microservice"
def verify_multi_part(
@@ -21,9 +22,9 @@ defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do
"optimizationRuns" => _,
"libraries" => _
} = body,
- address_hash
+ metadata
) do
- http_post_request(multiple_files_verification_url(), append_metadata(body, address_hash))
+ http_post_request(solidity_multiple_files_verification_url(), append_metadata(body, metadata), true)
end
def verify_standard_json_input(
@@ -33,9 +34,9 @@ defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do
"compilerVersion" => _,
"input" => _
} = body,
- address_hash
+ metadata
) do
- http_post_request(standard_json_input_verification_url(), append_metadata(body, address_hash))
+ http_post_request(solidity_standard_json_verification_url(), append_metadata(body, metadata), true)
end
def vyper_verify_multipart(
@@ -45,15 +46,29 @@ defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do
"compilerVersion" => _,
"sourceFiles" => _
} = body,
- address_hash
+ metadata
) do
- http_post_request(vyper_multiple_files_verification_url(), append_metadata(body, address_hash))
+ http_post_request(vyper_multiple_files_verification_url(), append_metadata(body, metadata), true)
end
- def http_post_request(url, body) do
+ def vyper_verify_standard_json(
+ %{
+ "bytecode" => _,
+ "bytecodeType" => _,
+ "compilerVersion" => _,
+ "input" => _
+ } = body,
+ metadata
+ ) do
+ http_post_request(vyper_standard_json_verification_url(), append_metadata(body, metadata), true)
+ end
+
+ def http_post_request(url, body, is_verification_request? \\ false) do
headers = [{"Content-Type", "application/json"}]
- case HTTPoison.post(url, Jason.encode!(body), headers, recv_timeout: @post_timeout) do
+ case HTTPoison.post(url, Jason.encode!(body), maybe_put_api_key_header(headers, is_verification_request?),
+ recv_timeout: @post_timeout
+ ) do
{:ok, %Response{body: body, status_code: _}} ->
process_verifier_response(body)
@@ -73,6 +88,18 @@ defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do
end
end
+ defp maybe_put_api_key_header(headers, false), do: headers
+
+ defp maybe_put_api_key_header(headers, true) do
+ api_key = Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour)[:api_key]
+
+ if api_key do
+ [{"x-api-key", api_key} | headers]
+ else
+ headers
+ end
+ end
+
def http_get_request(url) do
case HTTPoison.get(url) do
{:ok, %Response{body: body, status_code: 200}} ->
@@ -127,11 +154,15 @@ defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do
def process_verifier_response(other), do: {:error, other}
- def multiple_files_verification_url, do: "#{base_api_url()}" <> "/verifier/solidity/sources:verify-multi-part"
+ def solidity_multiple_files_verification_url,
+ do: "#{base_api_url()}" <> "/verifier/solidity/sources:verify-multi-part"
def vyper_multiple_files_verification_url, do: "#{base_api_url()}" <> "/verifier/vyper/sources:verify-multi-part"
- def standard_json_input_verification_url,
+ def vyper_standard_json_verification_url,
+ do: "#{base_api_url()}" <> "/verifier/vyper/sources:verify-standard-json"
+
+ def solidity_standard_json_verification_url,
do: "#{base_api_url()}" <> "/verifier/solidity/sources:verify-standard-json"
def versions_list_url, do: "#{base_api_url()}" <> "/verifier/solidity/versions"
@@ -141,17 +172,14 @@ defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do
def base_api_url, do: "#{base_url()}" <> "/api/v2"
def base_url do
- RustService.base_url(Explorer.SmartContract.RustVerifierInterfaceBehaviour)
+ Microservice.base_url(Explorer.SmartContract.RustVerifierInterfaceBehaviour)
end
def enabled?, do: Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour)[:enabled]
- defp append_metadata(body, address_hash) when is_map(body) do
+ defp append_metadata(body, metadata) when is_map(body) do
body
- |> Map.put("metadata", %{
- "chainId" => Application.get_env(:block_scout_web, :chain_id),
- "contractAddress" => to_string(address_hash)
- })
+ |> Map.put("metadata", metadata)
end
end
end
diff --git a/apps/explorer/lib/explorer/smart_contract/sig_provider_interface.ex b/apps/explorer/lib/explorer/smart_contract/sig_provider_interface.ex
index 92bba0d0ec70..b97f9bcd1edf 100644
--- a/apps/explorer/lib/explorer/smart_contract/sig_provider_interface.ex
+++ b/apps/explorer/lib/explorer/smart_contract/sig_provider_interface.ex
@@ -3,7 +3,7 @@ defmodule Explorer.SmartContract.SigProviderInterface do
Adapter for decoding events and function calls with https://github.com/blockscout/blockscout-rs/tree/main/sig-provider
"""
- alias Explorer.Utility.RustService
+ alias Explorer.Utility.Microservice
alias HTTPoison.Response
require Logger
@@ -81,7 +81,7 @@ defmodule Explorer.SmartContract.SigProviderInterface do
def base_api_url, do: "#{base_url()}" <> "/api/v1/abi"
def base_url do
- RustService.base_url(__MODULE__)
+ Microservice.base_url(__MODULE__)
end
def enabled?, do: Application.get_env(:explorer, __MODULE__)[:enabled]
diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex b/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex
index b08e64c9704f..b63d01c57e62 100644
--- a/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex
+++ b/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex
@@ -76,14 +76,14 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do
code = Keyword.fetch!(params, :code)
optimize = Keyword.fetch!(params, :optimize)
optimization_runs = optimization_runs(params)
- evm_version = Keyword.get(params, :evm_version, List.last(allowed_evm_versions()))
+ evm_version = Keyword.get(params, :evm_version, List.last(evm_versions(:solidity)))
bytecode_hash = Keyword.get(params, :bytecode_hash, "default")
external_libs = Keyword.get(params, :external_libs, %{})
external_libs_string = Jason.encode!(external_libs)
checked_evm_version =
- if evm_version in allowed_evm_versions() do
+ if evm_version in evm_versions(:solidity) do
evm_version
else
"byzantium"
@@ -262,9 +262,19 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do
end
end
- def allowed_evm_versions do
+ def evm_versions(compiler_type) do
+ case compiler_type do
+ :vyper ->
+ allowed_evm_versions(:allowed_vyper_evm_versions)
+
+ :solidity ->
+ allowed_evm_versions(:allowed_solidity_evm_versions)
+ end
+ end
+
+ defp allowed_evm_versions(env_name) do
:explorer
- |> Application.get_env(:allowed_evm_versions)
+ |> Application.get_env(env_name)
|> String.split(",")
|> Enum.map(fn version -> String.trim(version) end)
end
diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/publish_helper.ex b/apps/explorer/lib/explorer/smart_contract/solidity/publish_helper.ex
index 54a996ee8285..4af55016fc73 100644
--- a/apps/explorer/lib/explorer/smart_contract/solidity/publish_helper.ex
+++ b/apps/explorer/lib/explorer/smart_contract/solidity/publish_helper.ex
@@ -4,9 +4,9 @@ defmodule Explorer.SmartContract.Solidity.PublishHelper do
"""
alias Ecto.Changeset
- alias Explorer.Chain
+ alias Explorer.Chain.{Address, SmartContract}
alias Explorer.Chain.Events.Publisher, as: EventsPublisher
- alias Explorer.Chain.SmartContract
+ alias Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand
alias Explorer.SmartContract.Solidity.Publisher
alias Explorer.ThirdPartyIntegrations.Sourcify
@@ -88,9 +88,9 @@ defmodule Explorer.SmartContract.Solidity.PublishHelper do
)
end
- def prepare_files_array(files) do
- if is_map(files), do: Enum.map(files, fn {_, file} -> file end), else: []
- end
+ def prepare_files_array(files) when is_map(files), do: Map.values(files)
+
+ def prepare_files_array(_), do: []
def get_one_json(files_array) do
files_array
@@ -148,26 +148,41 @@ defmodule Explorer.SmartContract.Solidity.PublishHelper do
end
def check_and_verify(address_hash_string) do
- if Chain.smart_contract_fully_verified?(address_hash_string) do
- {:ok, :already_fully_verified}
+ if Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour)[:eth_bytecode_db?] do
+ LookUpSmartContractSourcesOnDemand.trigger_fetch(%Address{hash: address_hash_string}, nil)
else
- check_and_verify_inner(address_hash_string)
+ if Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled] do
+ check_by_address_in_sourcify(
+ SmartContract.select_partially_verified_by_address_hash(address_hash_string),
+ address_hash_string
+ )
+ else
+ {:error, :sourcify_disabled}
+ end
end
end
- defp check_and_verify_inner(address_hash_string) do
- if Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled] do
- if Chain.smart_contract_verified?(address_hash_string) do
- check_by_address_in_sourcify_if_contract_verified(address_hash_string)
- else
- check_by_address_in_sourcify_else(address_hash_string)
- end
- else
- {:error, :sourcify_disabled}
+ def sourcify_check(address_hash_string) do
+ cond do
+ Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour)[:eth_bytecode_db?] ->
+ {:error, :eth_bytecode_db_enabled}
+
+ Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled] ->
+ check_by_address_in_sourcify(
+ SmartContract.select_partially_verified_by_address_hash(address_hash_string),
+ address_hash_string
+ )
+
+ true ->
+ {:error, :sourcify_disabled}
end
end
- defp check_by_address_in_sourcify_if_contract_verified(address_hash_string) do
+ defp check_by_address_in_sourcify(false, _address_hash_string) do
+ {:ok, :already_fully_verified}
+ end
+
+ defp check_by_address_in_sourcify(true, address_hash_string) do
case Sourcify.check_by_address(address_hash_string) do
{:ok, _verified_status} ->
get_metadata_and_publish(address_hash_string, nil)
@@ -177,7 +192,7 @@ defmodule Explorer.SmartContract.Solidity.PublishHelper do
end
end
- defp check_by_address_in_sourcify_else(address_hash_string) do
+ defp check_by_address_in_sourcify(nil, address_hash_string) do
case Sourcify.check_by_address_any(address_hash_string) do
{:ok, "full", metadata} ->
process_metadata_and_publish(address_hash_string, metadata, false, false)
diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex b/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex
index 88381a7d3f66..b83895a15e46 100644
--- a/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex
+++ b/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex
@@ -3,13 +3,18 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
Module responsible to control the contract verification.
"""
+ require Logger
+
import Explorer.SmartContract.Helper, only: [cast_libraries: 1]
- alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.{CompilerVersion, Helper}
alias Explorer.SmartContract.Solidity.Verifier
+ @sc_verification_via_flattened_file_started "Smart-contract verification via flattened file started"
+ @sc_verification_via_standard_json_input_started "Smart-contract verification via standard json input started"
+ @sc_verification_via_multipart_files_started "Smart-contract verification via multipart files started"
+
@doc """
Evaluates smart contract authenticity and saves its details.
@@ -27,6 +32,7 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
"""
def publish(address_hash, params, external_libraries \\ %{}) do
+ Logger.info(@sc_verification_via_flattened_file_started)
params_with_external_libraries = add_external_libraries(params, external_libraries)
case Verifier.evaluate_authenticity(address_hash, params_with_external_libraries) do
@@ -65,6 +71,8 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
end
def publish_with_standard_json_input(%{"address_hash" => address_hash} = params, json_input) do
+ Logger.info(@sc_verification_via_standard_json_input_started)
+
case Verifier.evaluate_authenticity_via_standard_json_input(address_hash, params, json_input) do
{:ok,
%{
@@ -102,6 +110,7 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
end
def publish_with_multi_part_files(%{"address_hash" => address_hash} = params, external_libraries \\ %{}, files) do
+ Logger.info(@sc_verification_via_multipart_files_started)
params_with_external_libraries = add_external_libraries(params, external_libraries)
case Verifier.evaluate_authenticity_via_multi_part_files(address_hash, params_with_external_libraries, files) do
@@ -133,11 +142,13 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
"contractName" => contract_name,
"fileName" => file_name,
"sourceFiles" => sources,
- "compilerSettings" => compiler_settings_string
- },
+ "compilerSettings" => compiler_settings_string,
+ "matchType" => match_type
+ } = source,
address_hash,
is_standard_json?,
- save_file_path?
+ save_file_path?,
+ automatically_verified? \\ false
) do
secondary_sources =
for {file, source} <- sources,
@@ -163,6 +174,9 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
|> Map.put("file_path", if(save_file_path?, do: file_name))
|> Map.put("secondary_sources", secondary_sources)
|> Map.put("compiler_settings", if(is_standard_json?, do: compiler_settings))
+ |> Map.put("partially_verified", match_type == "PARTIAL")
+ |> Map.put("verified_via_eth_bytecode_db", automatically_verified?)
+ |> Map.put("verified_via_sourcify", source["sourcify?"])
publish_smart_contract(address_hash, prepared_params, Jason.decode!(abi_string || "null"))
end
@@ -183,14 +197,16 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
end
defp create_or_update_smart_contract(address_hash, attrs) do
- if Chain.smart_contract_verified?(address_hash) do
- Chain.update_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources)
+ Logger.info("Publish successfully verified Solidity smart-contract #{address_hash} into the DB")
+
+ if SmartContract.verified?(address_hash) do
+ SmartContract.update_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources)
else
- Chain.create_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources)
+ SmartContract.create_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources)
end
end
- defp unverified_smart_contract(address_hash, params, error, error_message, json_verification \\ false) do
+ defp unverified_smart_contract(address_hash, params, error, error_message, verification_with_files? \\ false) do
attrs =
address_hash
|> attributes(params)
@@ -202,9 +218,11 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
attrs,
error,
error_message,
- json_verification
+ verification_with_files?
)
+ Logger.error("Solidity smart-contract verification #{address_hash} failed because of the error #{error}")
+
%{changeset | action: :insert}
end
@@ -247,12 +265,13 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
external_libraries: prepared_external_libraries,
secondary_sources: params["secondary_sources"],
abi: abi,
- verified_via_sourcify: params["verified_via_sourcify"],
- partially_verified: params["partially_verified"],
+ verified_via_sourcify: params["verified_via_sourcify"] || false,
+ partially_verified: params["partially_verified"] || false,
is_vyper_contract: false,
autodetect_constructor_args: params["autodetect_constructor_args"],
is_yul: params["is_yul"] || false,
- compiler_settings: clean_compiler_settings
+ compiler_settings: clean_compiler_settings,
+ verified_via_eth_bytecode_db: params["verified_via_eth_bytecode_db"] || false
}
end
diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex b/apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex
index b8dbc3f81dba..b9ae134d54cb 100644
--- a/apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex
+++ b/apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex
@@ -3,6 +3,8 @@ defmodule Explorer.SmartContract.Solidity.PublisherWorker do
Background smart contract verification worker.
"""
+ require Logger
+
use Que.Worker, concurrency: 5
alias Explorer.Chain.Events.Publisher, as: EventsPublisher
@@ -68,6 +70,8 @@ defmodule Explorer.SmartContract.Solidity.PublisherWorker do
{:error, changeset}
end
+ Logger.info("Smart-contract #{address_hash} verification: broadcast verification results")
+
if conn do
EventsPublisher.broadcast([{:contract_verification_result, {address_hash, result, conn}}], :on_demand)
else
diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex b/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex
index a41847ad3c82..8eb602ba3559 100644
--- a/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex
+++ b/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex
@@ -9,10 +9,15 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
"""
import Explorer.SmartContract.Helper,
- only: [cast_libraries: 1, prepare_bytecode_for_microservice: 3, contract_creation_input: 1]
+ only: [
+ cast_libraries: 1,
+ fetch_data_for_verification: 1,
+ prepare_bytecode_for_microservice: 3
+ ]
alias ABI.{FunctionSelector, TypeDecoder}
alias Explorer.Chain
+ alias Explorer.Chain.{Data, Hash, SmartContract}
alias Explorer.SmartContract.RustVerifierInterface
alias Explorer.SmartContract.Solidity.CodeCompiler
@@ -38,9 +43,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
end
defp evaluate_authenticity_inner(true, address_hash, params) do
- deployed_bytecode = Chain.smart_contract_bytecode(address_hash)
-
- creation_tx_input = contract_creation_input(address_hash)
+ {creation_tx_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash)
%{}
|> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode)
@@ -52,14 +55,14 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
|> Map.put("optimizationRuns", prepare_optimization_runs(params["optimization"], params["optimization_runs"]))
|> Map.put("evmVersion", Map.get(params, "evm_version", "default"))
|> Map.put("compilerVersion", params["compiler_version"])
- |> RustVerifierInterface.verify_multi_part(address_hash)
+ |> RustVerifierInterface.verify_multi_part(verifier_metadata)
end
defp evaluate_authenticity_inner(false, address_hash, params) do
if is_nil(params["name"]) or params["name"] == "" do
{:error, :name}
else
- latest_evm_version = List.last(CodeCompiler.allowed_evm_versions())
+ latest_evm_version = List.last(CodeCompiler.evm_versions(:solidity))
evm_version = Map.get(params, "evm_version", latest_evm_version)
all_versions = [evm_version | previous_evm_versions(evm_version)]
@@ -122,14 +125,12 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
end
def evaluate_authenticity_via_standard_json_input_inner(true, address_hash, params, json_input) do
- deployed_bytecode = Chain.smart_contract_bytecode(address_hash)
-
- creation_tx_input = contract_creation_input(address_hash)
+ {creation_tx_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash)
%{"compilerVersion" => params["compiler_version"]}
|> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode)
|> Map.put("input", json_input)
- |> RustVerifierInterface.verify_standard_json_input(address_hash)
+ |> RustVerifierInterface.verify_standard_json_input(verifier_metadata)
end
def evaluate_authenticity_via_standard_json_input_inner(false, address_hash, params, json_input) do
@@ -137,9 +138,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
end
def evaluate_authenticity_via_multi_part_files(address_hash, params, files) do
- deployed_bytecode = Chain.smart_contract_bytecode(address_hash)
-
- creation_tx_input = contract_creation_input(address_hash)
+ {creation_tx_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash)
%{}
|> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode)
@@ -148,7 +147,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
|> Map.put("optimizationRuns", prepare_optimization_runs(params["optimization"], params["optimization_runs"]))
|> Map.put("evmVersion", Map.get(params, "evm_version", "default"))
|> Map.put("compilerVersion", params["compiler_version"])
- |> RustVerifierInterface.verify_multi_part(address_hash)
+ |> RustVerifierInterface.verify_multi_part(verifier_metadata)
end
defp verify(address_hash, params, json_input) do
@@ -505,19 +504,19 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
end
def previous_evm_versions(current_evm_version) do
- index = Enum.find_index(CodeCompiler.allowed_evm_versions(), fn el -> el == current_evm_version end)
+ index = Enum.find_index(CodeCompiler.evm_versions(:solidity), fn el -> el == current_evm_version end)
cond do
index == 0 ->
[]
index == 1 ->
- [List.first(CodeCompiler.allowed_evm_versions())]
+ [List.first(CodeCompiler.evm_versions(:solidity))]
true ->
[
- Enum.at(CodeCompiler.allowed_evm_versions(), index - 1),
- Enum.at(CodeCompiler.allowed_evm_versions(), index - 2)
+ Enum.at(CodeCompiler.evm_versions(:solidity), index - 1),
+ Enum.at(CodeCompiler.evm_versions(:solidity), index - 2)
]
end
end
@@ -533,4 +532,53 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
def parse_boolean(false), do: false
def parse_boolean(_), do: false
+
+ @doc """
+ Function tries to parse constructor args from smart contract creation input.
+ 1. using `extract_meta_from_deployed_bytecode/1` we derive CBOR metadata string
+ 2. using metadata we split creation_tx_input and try to decode resulting constructor arguments
+ 2.1. if we successfully decoded args using constructor's abi, then return constructor args
+ 2.2 otherwise return nil
+ """
+ @spec parse_constructor_arguments_for_sourcify_contract(Hash.Address.t(), SmartContract.abi()) :: nil | binary
+ def parse_constructor_arguments_for_sourcify_contract(address_hash, abi) do
+ parse_constructor_arguments_for_sourcify_contract(address_hash, abi, Chain.smart_contract_bytecode(address_hash))
+ end
+
+ @doc """
+ Clause for cases when we already can pass deployed bytecode to this function (in order to avoid excessive read DB accesses)
+ """
+ @spec parse_constructor_arguments_for_sourcify_contract(
+ Hash.Address.t(),
+ SmartContract.abi(),
+ binary | Explorer.Chain.Data.t()
+ ) :: nil | binary
+ def parse_constructor_arguments_for_sourcify_contract(address_hash, abi, deployed_bytecode)
+ when is_binary(deployed_bytecode) do
+ creation_tx_input =
+ case Chain.smart_contract_creation_tx_bytecode(address_hash) do
+ %{init: init, created_contract_code: _created_contract_code} ->
+ "0x" <> init_without_0x = init
+ init_without_0x
+
+ _ ->
+ nil
+ end
+
+ with true <- has_constructor_with_params?(abi),
+ check_function <- parse_constructor_and_return_check_function(abi),
+ false <- is_nil(creation_tx_input) || deployed_bytecode == "0x",
+ {meta, meta_length} <- extract_meta_from_deployed_bytecode(deployed_bytecode),
+ [_bytecode, constructor_args] <- String.split(creation_tx_input, meta <> meta_length),
+ ^constructor_args <- check_function.(constructor_args) do
+ constructor_args
+ else
+ _ ->
+ nil
+ end
+ end
+
+ def parse_constructor_arguments_for_sourcify_contract(address_hash, abi, deployed_bytecode) do
+ parse_constructor_arguments_for_sourcify_contract(address_hash, abi, Data.to_string(deployed_bytecode))
+ end
end
diff --git a/apps/explorer/lib/explorer/smart_contract/vyper/code_compiler.ex b/apps/explorer/lib/explorer/smart_contract/vyper/code_compiler.ex
index ae932cd52dc7..52e8248c612d 100644
--- a/apps/explorer/lib/explorer/smart_contract/vyper/code_compiler.ex
+++ b/apps/explorer/lib/explorer/smart_contract/vyper/code_compiler.ex
@@ -5,8 +5,6 @@ defmodule Explorer.SmartContract.Vyper.CodeCompiler do
alias Explorer.SmartContract.VyperDownloader
- require Logger
-
@spec run(Keyword.t()) :: {:ok, map} | {:error, :compilation | :name}
def run(params) do
compiler_version = Keyword.fetch!(params, :compiler_version)
diff --git a/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex b/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex
index 22613046682a..7a6087d39063 100644
--- a/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex
+++ b/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex
@@ -5,7 +5,8 @@ defmodule Explorer.SmartContract.Vyper.Publisher do
import Explorer.SmartContract.Helper, only: [cast_libraries: 1]
- alias Explorer.Chain
+ require Logger
+
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.CompilerVersion
alias Explorer.SmartContract.Vyper.Verifier
@@ -24,7 +25,7 @@ defmodule Explorer.SmartContract.Vyper.Publisher do
"compilerSettings" => _compiler_settings_string
} = source
} ->
- process_rust_verifier_response(source, address_hash, false)
+ process_rust_verifier_response(source, address_hash, false, false)
{:ok, %{abi: abi}} ->
publish_smart_contract(address_hash, params, abi)
@@ -38,7 +39,15 @@ defmodule Explorer.SmartContract.Vyper.Publisher do
end
def publish(address_hash, params, files) do
- case Verifier.evaluate_authenticity(address_hash, params, files) do
+ publish_inner(:evaluate_authenticity, [address_hash, params, files], address_hash, params, false)
+ end
+
+ def publish_standard_json(%{"address_hash" => address_hash} = params) do
+ publish_inner(:evaluate_authenticity_standard_json, [params], address_hash, params, true)
+ end
+
+ defp publish_inner(fun, args, address_hash, params, standard_json?) do
+ case apply(Verifier, fun, args) do
{
:ok,
%{
@@ -51,16 +60,16 @@ defmodule Explorer.SmartContract.Vyper.Publisher do
"compilerSettings" => _compiler_settings_string
} = source
} ->
- process_rust_verifier_response(source, address_hash, true)
+ process_rust_verifier_response(source, address_hash, true, standard_json?)
{:ok, %{abi: abi}} ->
publish_smart_contract(address_hash, params, abi)
{:error, error} ->
- {:error, unverified_smart_contract(address_hash, params, error, nil)}
+ {:error, unverified_smart_contract(address_hash, params, error, nil, true)}
_ ->
- {:error, unverified_smart_contract(address_hash, params, "Unexpected error", nil)}
+ {:error, unverified_smart_contract(address_hash, params, "Unexpected error", nil, true)}
end
end
@@ -72,10 +81,13 @@ defmodule Explorer.SmartContract.Vyper.Publisher do
"contractName" => contract_name,
"fileName" => file_name,
"sourceFiles" => sources,
- "compilerSettings" => compiler_settings_string
+ "compilerSettings" => compiler_settings_string,
+ "matchType" => match_type
},
address_hash,
- save_file_path?
+ save_file_path?,
+ standard_json?,
+ automatically_verified? \\ false
) do
secondary_sources =
for {file, source} <- sources,
@@ -95,18 +107,26 @@ defmodule Explorer.SmartContract.Vyper.Publisher do
|> Map.put("name", contract_name)
|> Map.put("file_path", if(save_file_path?, do: file_name))
|> Map.put("secondary_sources", secondary_sources)
- |> Map.put("evm_version", compiler_settings["evmVersion"] || "istanbul")
+ |> Map.put("evm_version", compiler_settings["evmVersion"])
+ |> Map.put("partially_verified", match_type == "PARTIAL")
+ |> Map.put("verified_via_eth_bytecode_db", automatically_verified?)
+ |> Map.put(
+ "optimization",
+ if(is_nil(compiler_settings["optimize"]), do: true, else: compiler_settings["optimize"])
+ )
+ |> Map.put("compiler_settings", if(standard_json?, do: compiler_settings))
publish_smart_contract(address_hash, prepared_params, Jason.decode!(abi_string))
end
def publish_smart_contract(address_hash, params, abi) do
+ Logger.info("Publish successfully verified Vyper smart-contract #{address_hash} into the DB")
attrs = address_hash |> attributes(params, abi)
- Chain.create_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources)
+ SmartContract.create_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources)
end
- defp unverified_smart_contract(address_hash, params, error, error_message) do
+ defp unverified_smart_contract(address_hash, params, error, error_message, verification_with_files? \\ false) do
attrs = attributes(address_hash, params)
changeset =
@@ -114,14 +134,18 @@ defmodule Explorer.SmartContract.Vyper.Publisher do
%SmartContract{address_hash: address_hash},
attrs,
error,
- error_message
+ error_message,
+ verification_with_files?
)
+ Logger.error("Vyper smart-contract verification #{address_hash} failed because of the error #{error}")
+
%{changeset | action: :insert}
end
defp attributes(address_hash, params, abi \\ %{}) do
constructor_arguments = params["constructor_arguments"]
+ compiler_settings = params["compiler_settings"]
clean_constructor_arguments =
if constructor_arguments != nil && constructor_arguments != "" do
@@ -130,24 +154,35 @@ defmodule Explorer.SmartContract.Vyper.Publisher do
nil
end
+ clean_compiler_settings =
+ if compiler_settings in ["", nil, %{}] do
+ nil
+ else
+ compiler_settings
+ end
+
compiler_version = CompilerVersion.get_strict_compiler_version(:vyper, params["compiler_version"])
+ optimization = if is_nil(params["optimization"]), do: true, else: params["optimization"]
+
%{
address_hash: address_hash,
- name: "Vyper_contract",
+ name: params["name"],
compiler_version: compiler_version,
evm_version: params["evm_version"],
optimization_runs: nil,
- optimization: false,
+ optimization: optimization,
contract_source_code: params["contract_source_code"],
constructor_arguments: clean_constructor_arguments,
external_libraries: [],
- secondary_sources: [],
+ secondary_sources: params["secondary_sources"],
abi: abi,
verified_via_sourcify: false,
- partially_verified: false,
+ partially_verified: params["partially_verified"] || false,
is_vyper_contract: true,
- file_path: params["file_path"]
+ file_path: params["file_path"],
+ verified_via_eth_bytecode_db: params["verified_via_eth_bytecode_db"] || false,
+ compiler_settings: clean_compiler_settings
}
end
end
diff --git a/apps/explorer/lib/explorer/smart_contract/vyper/publisher_worker.ex b/apps/explorer/lib/explorer/smart_contract/vyper/publisher_worker.ex
index fe77a8e246db..690efc346635 100644
--- a/apps/explorer/lib/explorer/smart_contract/vyper/publisher_worker.ex
+++ b/apps/explorer/lib/explorer/smart_contract/vyper/publisher_worker.ex
@@ -3,26 +3,32 @@ defmodule Explorer.SmartContract.Vyper.PublisherWorker do
Background smart contract verification worker.
"""
+ require Logger
+
use Que.Worker, concurrency: 5
alias Explorer.Chain.Events.Publisher, as: EventsPublisher
alias Explorer.SmartContract.Vyper.Publisher
- def perform({address_hash, params, %Plug.Conn{} = conn}) do
- broadcast(address_hash, [address_hash, params], conn)
+ def perform({"vyper_standard_json", params}) do
+ broadcast(params["address_hash"], [params], :publish_standard_json)
+ end
+
+ def perform({"vyper_multipart", params, files}) do
+ broadcast(params["address_hash"], [params["address_hash"], params, files], :publish)
end
- def perform({address_hash, params, files}) do
- broadcast(address_hash, [address_hash, params, files])
+ def perform({"vyper_flattened", params}) do
+ broadcast(params["address_hash"], [params["address_hash"], params], :publish)
end
- def perform({address_hash, params}) do
- broadcast(address_hash, [address_hash, params])
+ def perform({address_hash, params, %Plug.Conn{} = conn}) do
+ broadcast(address_hash, [address_hash, params], :publish, conn)
end
- defp broadcast(address_hash, args, conn \\ nil) do
+ defp broadcast(address_hash, args, function, conn \\ nil) do
result =
- case apply(Publisher, :publish, args) do
+ case apply(Publisher, function, args) do
{:ok, _contract} = result ->
result
@@ -30,6 +36,8 @@ defmodule Explorer.SmartContract.Vyper.PublisherWorker do
{:error, changeset}
end
+ Logger.info("Smart-contract #{address_hash} verification: broadcast verification results")
+
if conn do
EventsPublisher.broadcast([{:contract_verification_result, {address_hash, result, conn}}], :on_demand)
else
diff --git a/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex b/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex
index 3042d0e5eeef..3ca5ac90994c 100644
--- a/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex
+++ b/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex
@@ -9,10 +9,11 @@ defmodule Explorer.SmartContract.Vyper.Verifier do
"""
require Logger
- alias Explorer.Chain
alias Explorer.SmartContract.Vyper.CodeCompiler
alias Explorer.SmartContract.RustVerifierInterface
- import Explorer.SmartContract.Helper, only: [prepare_bytecode_for_microservice: 3, contract_creation_input: 1]
+
+ import Explorer.SmartContract.Helper,
+ only: [fetch_data_for_verification: 1, prepare_bytecode_for_microservice: 3, contract_creation_input: 1]
def evaluate_authenticity(_, %{"contract_source_code" => ""}),
do: {:error, :contract_source_code}
@@ -34,11 +35,7 @@ defmodule Explorer.SmartContract.Vyper.Verifier do
def evaluate_authenticity(address_hash, params, files) do
try do
if RustVerifierInterface.enabled?() do
- deployed_bytecode = Chain.smart_contract_bytecode(address_hash)
-
- creation_tx_input = contract_creation_input(address_hash)
-
- vyper_verify_multipart(params, creation_tx_input, deployed_bytecode, params["evm_version"], files, address_hash)
+ vyper_verify_multipart(params, params["evm_version"], files, address_hash)
end
rescue
exception ->
@@ -51,15 +48,25 @@ defmodule Explorer.SmartContract.Vyper.Verifier do
end
end
- defp evaluate_authenticity_inner(true, address_hash, params) do
- deployed_bytecode = Chain.smart_contract_bytecode(address_hash)
-
- creation_tx_input = contract_creation_input(address_hash)
+ def evaluate_authenticity_standard_json(%{"address_hash" => address_hash} = params) do
+ try do
+ if RustVerifierInterface.enabled?() do
+ vyper_verify_standard_json(params, address_hash)
+ end
+ rescue
+ exception ->
+ Logger.error(fn ->
+ [
+ "Error while verifying standard-json vyper smart-contract address: #{address_hash}, params: #{inspect(params, limit: :infinity, printable_limit: :infinity)}: ",
+ Exception.format(:error, exception)
+ ]
+ end)
+ end
+ end
+ defp evaluate_authenticity_inner(true, address_hash, params) do
vyper_verify_multipart(
params,
- creation_tx_input,
- deployed_bytecode,
params["evm_version"],
%{
"#{params["name"]}.vy" => params["contract_source_code"]
@@ -109,12 +116,25 @@ defmodule Explorer.SmartContract.Vyper.Verifier do
end
end
- defp vyper_verify_multipart(params, creation_tx_input, deployed_bytecode, evm_version, files, address_hash) do
+ defp vyper_verify_multipart(params, evm_version, files, address_hash) do
+ {creation_tx_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash)
+
%{}
|> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode)
- |> Map.put("evmVersion", evm_version || "istanbul")
+ |> Map.put("evmVersion", evm_version)
|> Map.put("sourceFiles", files)
|> Map.put("compilerVersion", params["compiler_version"])
- |> RustVerifierInterface.vyper_verify_multipart(address_hash)
+ |> Map.put("interfaces", params["interfaces"] || %{})
+ |> RustVerifierInterface.vyper_verify_multipart(verifier_metadata)
+ end
+
+ defp vyper_verify_standard_json(params, address_hash) do
+ {creation_tx_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash)
+
+ %{}
+ |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode)
+ |> Map.put("compilerVersion", params["compiler_version"])
+ |> Map.put("input", params["input"])
+ |> RustVerifierInterface.vyper_verify_standard_json(verifier_metadata)
end
end
diff --git a/apps/explorer/lib/explorer/smart_contract/writer.ex b/apps/explorer/lib/explorer/smart_contract/writer.ex
index 07b77abd627b..6aef776a1ca5 100644
--- a/apps/explorer/lib/explorer/smart_contract/writer.ex
+++ b/apps/explorer/lib/explorer/smart_contract/writer.ex
@@ -3,7 +3,6 @@ defmodule Explorer.SmartContract.Writer do
Generates smart-contract transactions
"""
- alias Explorer.Chain
alias Explorer.Chain.{Hash, SmartContract}
alias Explorer.SmartContract.Helper
@@ -21,7 +20,7 @@ defmodule Explorer.SmartContract.Writer do
@spec write_functions_proxy(Hash.t() | String.t()) :: [%{}]
def write_functions_proxy(implementation_address_hash_string, options \\ []) do
- implementation_abi = Chain.get_implementation_abi(implementation_address_hash_string, options)
+ implementation_abi = SmartContract.get_smart_contract_abi(implementation_address_hash_string, options)
case implementation_abi do
nil ->
diff --git a/apps/explorer/lib/explorer/sorting_helper.ex b/apps/explorer/lib/explorer/sorting_helper.ex
new file mode 100644
index 000000000000..f6a478d85d78
--- /dev/null
+++ b/apps/explorer/lib/explorer/sorting_helper.ex
@@ -0,0 +1,168 @@
+defmodule Explorer.SortingHelper do
+ @moduledoc """
+ Module that order and paginate queries dynamically based on default and provided sorting parameters.
+ Example of sorting parameters:
+ ```
+ [{:asc, :fetched_coin_balance, :address}, {:dynamic, :contract_code_size, :desc, dynamic([t], fragment(LENGTH(?), t.contract_source_code))}, desc: :id]
+ ```
+ First list entry specify joined address table column as a column to order by and paginate, second entry
+ specifies name of a key in paging_options and arbitrary dynamic that will be used in ordering and pagination,
+ third entry specifies own column name to order by and paginate.
+ """
+ require Explorer.SortingHelper
+
+ alias Explorer.PagingOptions
+
+ import Ecto.Query
+
+ @typep ordering :: :asc | :asc_nulls_first | :asc_nulls_last | :desc | :desc_nulls_first | :desc_nulls_last
+ @typep column :: atom
+ @typep binding :: atom
+ @type sorting_params :: [
+ {ordering, column} | {ordering, column, binding} | {:dynamic, column, ordering, Ecto.Query.dynamic_expr()}
+ ]
+
+ @doc """
+ Applies sorting to query based on default sorting params and sorting params from the client,
+ these params merged keeping provided one over default one.
+ """
+ @spec apply_sorting(Ecto.Query.t(), sorting_params, sorting_params) :: Ecto.Query.t()
+ def apply_sorting(query, sorting, default_sorting) when is_list(sorting) and is_list(default_sorting) do
+ sorting |> merge_sorting_params_with_defaults(default_sorting) |> sorting_params_to_order_by(query)
+ end
+
+ defp merge_sorting_params_with_defaults([], default_sorting) when is_list(default_sorting), do: default_sorting
+
+ defp merge_sorting_params_with_defaults(sorting, default_sorting)
+ when is_list(sorting) and is_list(default_sorting) do
+ (sorting ++ default_sorting)
+ |> Enum.uniq_by(fn
+ {_, field} -> field
+ {_, field, as} -> {field, as}
+ {:dynamic, key_name, _, _} -> key_name
+ end)
+ end
+
+ defp sorting_params_to_order_by(sorting_params, query) do
+ sorting_params
+ |> Enum.reduce(query, fn
+ {:dynamic, _key_name, order, dynamic}, query -> query |> order_by(^[{order, dynamic}])
+ {order, column, binding}, query -> query |> order_by([{^order, field(as(^binding), ^column)}])
+ {order, column}, query -> query |> order_by(^[{order, column}])
+ end)
+ end
+
+ @doc """
+ Page the query based on paging options, default sorting params and sorting params from the client,
+ these params merged keeping provided one over default one.
+ """
+ @spec page_with_sorting(Ecto.Query.t(), PagingOptions.t(), sorting_params, sorting_params) :: Ecto.Query.t()
+ def page_with_sorting(query, %PagingOptions{key: key, page_size: page_size}, sorting, default_sorting)
+ when not is_nil(key) do
+ sorting
+ |> merge_sorting_params_with_defaults(default_sorting)
+ |> do_page_with_sorting()
+ |> case do
+ nil -> query
+ dynamic_where -> query |> where(^dynamic_where.(key))
+ end
+ |> limit_query(page_size)
+ end
+
+ def page_with_sorting(query, %PagingOptions{page_size: page_size}, _sorting, _default_sorting) do
+ query |> limit_query(page_size)
+ end
+
+ def page_with_sorting(query, _, _sorting, _default_sorting), do: query
+
+ defp limit_query(query, limit) when is_integer(limit), do: query |> limit(^limit)
+ defp limit_query(query, _), do: query
+
+ defp do_page_with_sorting([{order, column} | rest]) do
+ fn key -> page_by_column(key, column, order, do_page_with_sorting(rest)) end
+ end
+
+ defp do_page_with_sorting([{:dynamic, key_name, order, dynamic} | rest]) do
+ fn key -> page_by_column(key, {:dynamic, key_name, dynamic}, order, do_page_with_sorting(rest)) end
+ end
+
+ defp do_page_with_sorting([{order, column, binding} | rest]) do
+ fn key -> page_by_column(key, {column, binding}, order, do_page_with_sorting(rest)) end
+ end
+
+ defp do_page_with_sorting([]), do: nil
+
+ for {key_name, pattern, ecto_value} <- [
+ {quote(do: key_name), quote(do: {:dynamic, key_name, dynamic}), quote(do: ^dynamic)},
+ {quote(do: column), quote(do: {column, binding}), quote(do: field(as(^binding), ^column))},
+ {quote(do: column), quote(do: column), quote(do: field(t, ^column))}
+ ] do
+ defp page_by_column(key, unquote(pattern), :desc_nulls_last, next_column) do
+ case key[unquote(key_name)] do
+ nil ->
+ dynamic([t], is_nil(unquote(ecto_value)) and ^apply_next_column(next_column, key))
+
+ value ->
+ dynamic(
+ [t],
+ is_nil(unquote(ecto_value)) or unquote(ecto_value) < ^value or
+ (unquote(ecto_value) == ^value and ^apply_next_column(next_column, key))
+ )
+ end
+ end
+
+ defp page_by_column(key, unquote(pattern), :asc_nulls_first, next_column) do
+ case key[unquote(key_name)] do
+ nil ->
+ dynamic([t], not is_nil(unquote(ecto_value)) or ^apply_next_column(next_column, key))
+
+ value ->
+ dynamic(
+ [t],
+ not is_nil(unquote(ecto_value)) and
+ (unquote(ecto_value) > ^value or
+ (unquote(ecto_value) == ^value and ^apply_next_column(next_column, key)))
+ )
+ end
+ end
+
+ defp page_by_column(key, unquote(pattern), order, next_column) when order in ~w(asc asc_nulls_last)a do
+ case key[unquote(key_name)] do
+ nil ->
+ dynamic([t], is_nil(unquote(ecto_value)) and ^apply_next_column(next_column, key))
+
+ value ->
+ dynamic(
+ [t],
+ is_nil(unquote(ecto_value)) or
+ (unquote(ecto_value) > ^value or
+ (unquote(ecto_value) == ^value and ^apply_next_column(next_column, key)))
+ )
+ end
+ end
+
+ defp page_by_column(key, unquote(pattern), order, next_column)
+ when order in ~w(desc desc_nulls_first)a do
+ case key[unquote(key_name)] do
+ nil ->
+ dynamic([t], not is_nil(unquote(ecto_value)) or ^apply_next_column(next_column, key))
+
+ value ->
+ dynamic(
+ [t],
+ not is_nil(unquote(ecto_value)) and
+ (unquote(ecto_value) < ^value or
+ (unquote(ecto_value) == ^value and ^apply_next_column(next_column, key)))
+ )
+ end
+ end
+ end
+
+ defp apply_next_column(nil, _key) do
+ false
+ end
+
+ defp apply_next_column(next_column, key) do
+ next_column.(key)
+ end
+end
diff --git a/apps/explorer/lib/explorer/tags/address_tag.ex b/apps/explorer/lib/explorer/tags/address_tag.ex
index 617423560881..cfb61d3324b7 100644
--- a/apps/explorer/lib/explorer/tags/address_tag.ex
+++ b/apps/explorer/lib/explorer/tags/address_tag.ex
@@ -12,14 +12,13 @@ defmodule Explorer.Tags.AddressTag do
from: 2
]
- alias Explorer.Chain.Address
alias Explorer.Repo
alias Explorer.Tags.{AddressTag, AddressToTag}
@typedoc """
* `:id` - id of Tag
* `:label` - Tag's label
- * `:label` - Label's display name
+ * `:display_name` - Label's display name
"""
@type t :: %AddressTag{
label: String.t()
@@ -28,7 +27,6 @@ defmodule Explorer.Tags.AddressTag do
schema "address_tags" do
field(:label, :string)
field(:display_name, :string)
- has_many(:addresses, Address, foreign_key: :hash)
has_many(:tag_id, AddressToTag, foreign_key: :id)
timestamps()
diff --git a/apps/explorer/lib/explorer/third_party_integrations/auth0.ex b/apps/explorer/lib/explorer/third_party_integrations/auth0.ex
index 4afad8845fb0..2b5c6c67a6c5 100644
--- a/apps/explorer/lib/explorer/third_party_integrations/auth0.ex
+++ b/apps/explorer/lib/explorer/third_party_integrations/auth0.ex
@@ -1,6 +1,6 @@
defmodule Explorer.ThirdPartyIntegrations.Auth0 do
@moduledoc """
- Module for fetching jwt auth0 Management API (https://auth0.com/docs/api/management/v2) jwt
+ Module for fetching jwt Auth0 Management API (https://auth0.com/docs/api/management/v2) jwt
"""
@redis_key "auth0"
@@ -9,7 +9,9 @@ defmodule Explorer.ThirdPartyIntegrations.Auth0 do
Firstly it tries to access cached token and if there is no cached one, token will be requested from Auth0
"""
@spec get_m2m_jwt() :: nil | String.t()
- def get_m2m_jwt, do: get_m2m_jwt_inner(Redix.command(:redix, ["GET", @redis_key]))
+ def get_m2m_jwt do
+ get_m2m_jwt_inner(Redix.command(:redix, ["GET", cookie_key(@redis_key)]))
+ end
def get_m2m_jwt_inner({:ok, token}) when not is_nil(token), do: token
@@ -40,9 +42,22 @@ defmodule Explorer.ThirdPartyIntegrations.Auth0 do
end
end
- defp cache_token(token, ttl) do
+ @doc """
+ Generates key from chain_id and cookie hash for storing in Redis
+ """
+ @spec cookie_key(binary) :: String.t()
+ def cookie_key(hash) do
chain_id = Application.get_env(:block_scout_web, :chain_id)
- Redix.command(:redix, ["SET", "#{chain_id}_#{@redis_key}", token, "EX", ttl])
+
+ if chain_id do
+ chain_id <> "_" <> hash
+ else
+ hash
+ end
+ end
+
+ defp cache_token(token, ttl) do
+ Redix.command(:redix, ["SET", cookie_key(@redis_key), token, "EX", ttl])
token
end
end
diff --git a/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex b/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex
new file mode 100644
index 000000000000..dcf0e473b24e
--- /dev/null
+++ b/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex
@@ -0,0 +1,63 @@
+defmodule Explorer.ThirdPartyIntegrations.NovesFi do
+ @moduledoc """
+ Module for Noves.Fi API integration https://blockscout.noves.fi/swagger/index.html
+ """
+
+ alias Explorer.Helper
+ alias Explorer.Utility.Microservice
+
+ @recv_timeout 60_000
+
+ @doc """
+ Proxy request to noves.fi API endpoints
+ """
+ @spec noves_fi_api_request(String.t(), Plug.Conn.t()) :: {any(), integer()}
+ def noves_fi_api_request(url, conn) do
+ headers = [{"apiKey", api_key()}]
+ url_with_params = url <> "?" <> conn.query_string
+
+ case HTTPoison.get(url_with_params, headers, recv_timeout: @recv_timeout) do
+ {:ok, %HTTPoison.Response{status_code: status, body: body}} ->
+ {Helper.decode_json(body), status}
+
+ _ ->
+ {nil, 500}
+ end
+ end
+
+ @doc """
+ Noves.fi /evm/{chain}/tx/{txHash} endpoint
+ """
+ @spec tx_url(String.t()) :: String.t()
+ def tx_url(transaction_hash_string) do
+ "#{base_url()}/evm/#{chain_name()}/tx/#{transaction_hash_string}"
+ end
+
+ @doc """
+ Noves.fi /evm/{chain}/describeTx/{txHash} endpoint
+ """
+ @spec describe_tx_url(String.t()) :: String.t()
+ def describe_tx_url(transaction_hash_string) do
+ "#{base_url()}/evm/#{chain_name()}/describeTx/#{transaction_hash_string}"
+ end
+
+ @doc """
+ Noves.fi /evm/{chain}/txs/{accountAddress} endpoint
+ """
+ @spec address_txs_url(String.t()) :: String.t()
+ def address_txs_url(address_hash_string) do
+ "#{base_url()}/evm/#{chain_name()}/txs/#{address_hash_string}"
+ end
+
+ defp base_url do
+ Microservice.base_url(__MODULE__)
+ end
+
+ defp chain_name do
+ Application.get_env(:explorer, __MODULE__)[:chain_name]
+ end
+
+ defp api_key do
+ Application.get_env(:explorer, __MODULE__)[:api_key]
+ end
+end
diff --git a/apps/explorer/lib/explorer/third_party_integrations/solidityscan.ex b/apps/explorer/lib/explorer/third_party_integrations/solidityscan.ex
new file mode 100644
index 000000000000..40a5bffb9784
--- /dev/null
+++ b/apps/explorer/lib/explorer/third_party_integrations/solidityscan.ex
@@ -0,0 +1,40 @@
+defmodule Explorer.ThirdPartyIntegrations.SolidityScan do
+ @moduledoc """
+ Module for SolidityScan integration https://apidoc.solidityscan.com/solidityscan-security-api/solidityscan-other-apis/quickscan-api-v1
+ """
+
+ alias Explorer.Helper
+
+ @blockscout_platform_id "16"
+ @recv_timeout 60_000
+
+ @doc """
+ Proxy request to solidityscan API endpoint for the given smart-contract
+ """
+ @spec solidityscan_request(String.t()) :: any()
+ def solidityscan_request(address_hash_string) do
+ headers = [{"Authorization", "Token #{api_key()}"}]
+
+ url = base_url(address_hash_string)
+
+ case HTTPoison.get(url, headers, recv_timeout: @recv_timeout) do
+ {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
+ Helper.decode_json(body)
+
+ _ ->
+ nil
+ end
+ end
+
+ defp base_url(address_hash_string) do
+ "https://api.solidityscan.com/api/v1/quickscan/#{@blockscout_platform_id}/#{chain_id()}/#{address_hash_string}"
+ end
+
+ defp chain_id do
+ Application.get_env(:explorer, __MODULE__)[:chain_id]
+ end
+
+ defp api_key do
+ Application.get_env(:explorer, __MODULE__)[:api_key]
+ end
+end
diff --git a/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex b/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex
index b510fb355d49..605a50547ebd 100644
--- a/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex
+++ b/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex
@@ -4,6 +4,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
"""
use Tesla
+ alias Explorer.Helper, as: ExplorerHelper
alias Explorer.SmartContract.{Helper, RustVerifierInterface}
alias HTTPoison.{Error, Response}
alias Tesla.Multipart
@@ -223,7 +224,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
end
defp parse_verify_http_response(body) do
- body_json = decode_json(body)
+ body_json = ExplorerHelper.decode_json(body)
case body_json do
# Success status from native Sourcify server
@@ -246,7 +247,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
end
defp parse_check_by_address_http_response(body) do
- body_json = decode_json(body)
+ body_json = ExplorerHelper.decode_json(body)
case body_json do
[%{"status" => "perfect"}] ->
@@ -264,11 +265,11 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
end
defp parse_get_metadata_http_response(body) do
- body_json = decode_json(body)
+ body_json = ExplorerHelper.decode_json(body)
case body_json do
%{"message" => message, "errors" => errors} ->
- {:error, "#{message}: #{decode_json(errors)}"}
+ {:error, "#{message}: #{ExplorerHelper.decode_json(errors)}"}
metadata ->
{:ok, metadata}
@@ -276,11 +277,11 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
end
defp parse_get_metadata_any_http_response(body) do
- body_json = decode_json(body)
+ body_json = ExplorerHelper.decode_json(body)
case body_json do
%{"message" => message, "errors" => errors} ->
- {:error, "#{message}: #{decode_json(errors)}"}
+ {:error, "#{message}: #{ExplorerHelper.decode_json(errors)}"}
%{"status" => status, "files" => metadata} ->
{:ok, status, metadata}
@@ -290,16 +291,23 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
end
end
+ @invalid_json_response "invalid http error json response"
defp parse_http_error_response(body) do
- body_json = decode_json(body)
+ body_json = ExplorerHelper.decode_json(body)
if is_map(body_json) do
- {:error, body_json["error"]}
+ error = body_json["error"]
+
+ parse_http_error_response_internal(error)
else
- {:error, body}
+ parse_http_error_response_internal(body)
end
end
+ defp parse_http_error_response_internal(nil), do: {:error, @invalid_json_response}
+
+ defp parse_http_error_response_internal(data), do: {:error, data}
+
def parse_params_from_sourcify(address_hash_string, verification_metadata) do
filtered_files =
verification_metadata
@@ -350,7 +358,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
defp parse_json_from_sourcify_for_insertion(verification_metadata_json) do
%{"name" => _, "content" => content} = verification_metadata_json
- content_json = decode_json(content)
+ content_json = ExplorerHelper.decode_json(content)
compiler_version = "v" <> (content_json |> Map.get("compiler") |> Map.get("version"))
abi = content_json |> Map.get("output") |> Map.get("abi")
settings = Map.get(content_json, "settings")
@@ -401,12 +409,6 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
|> Map.put("contract_source_code", content)
end
- def decode_json(data) do
- Jason.decode!(data)
- rescue
- _ -> data
- end
-
defp config(module, key) do
:explorer
|> Application.get_env(module)
diff --git a/apps/explorer/lib/explorer/token_instance_owner_address_migration/helper.ex b/apps/explorer/lib/explorer/token_instance_owner_address_migration/helper.ex
new file mode 100644
index 000000000000..01bc78d39e28
--- /dev/null
+++ b/apps/explorer/lib/explorer/token_instance_owner_address_migration/helper.ex
@@ -0,0 +1,84 @@
+defmodule Explorer.TokenInstanceOwnerAddressMigration.Helper do
+ @moduledoc """
+ Auxiliary functions for TokenInstanceOwnerAddressMigration.{Worker and Supervisor}
+ """
+ import Ecto.Query,
+ only: [
+ from: 2
+ ]
+
+ alias Explorer.{Chain, Repo}
+ alias Explorer.Chain.Token.Instance
+ alias Explorer.Chain.{SmartContract, TokenTransfer}
+
+ {:ok, burn_address_hash} = Chain.string_to_address_hash(SmartContract.burn_address_hash_string())
+ @burn_address_hash burn_address_hash
+
+ @spec filtered_token_instances_query(non_neg_integer()) :: Ecto.Query.t()
+ def filtered_token_instances_query(limit) do
+ from(instance in Instance,
+ where: is_nil(instance.owner_address_hash),
+ inner_join: token in assoc(instance, :token),
+ where: token.type == "ERC-721",
+ limit: ^limit,
+ select: %{token_id: instance.token_id, token_contract_address_hash: instance.token_contract_address_hash}
+ )
+ end
+
+ @spec fetch_and_insert([map]) ::
+ {:error, :timeout | [map]}
+ | {:ok,
+ %{
+ :token_instances => [Instance.t()]
+ }}
+ | {:error, any, any, map}
+ def fetch_and_insert(batch) do
+ changes =
+ batch
+ |> Enum.map(&process_instance/1)
+ |> Enum.reject(&is_nil/1)
+
+ Chain.import(%{token_instances: %{params: changes}})
+ end
+
+ defp process_instance(%{token_id: token_id, token_contract_address_hash: token_contract_address_hash}) do
+ token_transfer_query =
+ from(tt in TokenTransfer.only_consensus_transfers_query(),
+ where:
+ tt.token_contract_address_hash == ^token_contract_address_hash and
+ fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^token_id),
+ order_by: [desc: tt.block_number, desc: tt.log_index],
+ limit: 1,
+ select: %{
+ token_contract_address_hash: tt.token_contract_address_hash,
+ token_ids: tt.token_ids,
+ to_address_hash: tt.to_address_hash,
+ block_number: tt.block_number,
+ log_index: tt.log_index
+ }
+ )
+
+ token_transfer =
+ Repo.one(token_transfer_query, timeout: :timer.minutes(5)) ||
+ %{to_address_hash: @burn_address_hash, block_number: -1, log_index: -1}
+
+ %{
+ token_contract_address_hash: token_contract_address_hash,
+ token_id: token_id,
+ token_type: "ERC-721",
+ owner_address_hash: token_transfer.to_address_hash,
+ owner_updated_at_block: token_transfer.block_number,
+ owner_updated_at_log_index: token_transfer.log_index
+ }
+ rescue
+ DBConnection.ConnectionError ->
+ nil
+ end
+
+ @spec unfilled_token_instances_exists? :: boolean
+ def unfilled_token_instances_exists? do
+ 1
+ |> filtered_token_instances_query()
+ |> Repo.exists?()
+ end
+end
diff --git a/apps/explorer/lib/explorer/token_instance_owner_address_migration/supervisor.ex b/apps/explorer/lib/explorer/token_instance_owner_address_migration/supervisor.ex
new file mode 100644
index 000000000000..01ca324f5f2d
--- /dev/null
+++ b/apps/explorer/lib/explorer/token_instance_owner_address_migration/supervisor.ex
@@ -0,0 +1,28 @@
+defmodule Explorer.TokenInstanceOwnerAddressMigration.Supervisor do
+ @moduledoc """
+ Supervisor for Explorer.TokenInstanceOwnerAddressMigration.Worker
+ """
+
+ use Supervisor
+
+ alias Explorer.TokenInstanceOwnerAddressMigration.Worker
+
+ def start_link(init_arg) do
+ Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
+ end
+
+ @impl true
+ def init(_init_arg) do
+ params = Application.get_env(:explorer, Explorer.TokenInstanceOwnerAddressMigration)
+
+ if params[:enabled] do
+ children = [
+ {Worker, params}
+ ]
+
+ Supervisor.init(children, strategy: :one_for_one)
+ else
+ :ignore
+ end
+ end
+end
diff --git a/apps/explorer/lib/explorer/token_instance_owner_address_migration/worker.ex b/apps/explorer/lib/explorer/token_instance_owner_address_migration/worker.ex
new file mode 100644
index 000000000000..c9cfabc9d2ca
--- /dev/null
+++ b/apps/explorer/lib/explorer/token_instance_owner_address_migration/worker.ex
@@ -0,0 +1,51 @@
+defmodule Explorer.TokenInstanceOwnerAddressMigration.Worker do
+ @moduledoc """
+ GenServer for filling owner_address_hash, owner_updated_at_block and owner_updated_at_log_index
+ for ERC-721 token instances. Works in the following way
+ 1. Checks if there are some unprocessed nfts.
+ - if yes, then go to 2 stage
+ - if no, then shutdown
+ 2. Fetch `(concurrency * batch_size)` token instances, process them in `concurrency` tasks.
+ 3. Go to step 1
+ """
+
+ use GenServer, restart: :transient
+
+ alias Explorer.Repo
+ alias Explorer.TokenInstanceOwnerAddressMigration.Helper
+
+ def start_link(concurrency: concurrency, batch_size: batch_size, enabled: _) do
+ GenServer.start_link(__MODULE__, %{concurrency: concurrency, batch_size: batch_size}, name: __MODULE__)
+ end
+
+ @impl true
+ def init(opts) do
+ GenServer.cast(__MODULE__, :check_necessity)
+
+ {:ok, opts}
+ end
+
+ @impl true
+ def handle_cast(:check_necessity, state) do
+ if Helper.unfilled_token_instances_exists?() do
+ GenServer.cast(__MODULE__, :backfill)
+ {:noreply, state}
+ else
+ {:stop, :normal, state}
+ end
+ end
+
+ @impl true
+ def handle_cast(:backfill, %{concurrency: concurrency, batch_size: batch_size} = state) do
+ (concurrency * batch_size)
+ |> Helper.filtered_token_instances_query()
+ |> Repo.all()
+ |> Enum.chunk_every(batch_size)
+ |> Enum.map(fn batch -> Task.async(fn -> Helper.fetch_and_insert(batch) end) end)
+ |> Task.await_many(:infinity)
+
+ GenServer.cast(__MODULE__, :check_necessity)
+
+ {:noreply, state}
+ end
+end
diff --git a/apps/explorer/lib/explorer/token_transfer_token_id_migration/lowest_block_number_updater.ex b/apps/explorer/lib/explorer/token_transfer_token_id_migration/lowest_block_number_updater.ex
deleted file mode 100644
index 5794e524ccfc..000000000000
--- a/apps/explorer/lib/explorer/token_transfer_token_id_migration/lowest_block_number_updater.ex
+++ /dev/null
@@ -1,65 +0,0 @@
-defmodule Explorer.TokenTransferTokenIdMigration.LowestBlockNumberUpdater do
- @moduledoc """
- Collects processed block numbers from token id migration workers
- and updates last_processed_block_number according to them.
- Full algorithm is in the 'Indexer.Fetcher.TokenTransferTokenIdMigration.Supervisor' module doc.
- """
- use GenServer
-
- alias Explorer.Utility.TokenTransferTokenIdMigratorProgress
-
- def start_link(_) do
- GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
- end
-
- @impl true
- def init(_) do
- last_processed_block_number = TokenTransferTokenIdMigratorProgress.get_last_processed_block_number()
-
- {:ok, %{last_processed_block_number: last_processed_block_number, processed_ranges: []}}
- end
-
- def add_range(from, to) do
- GenServer.cast(__MODULE__, {:add_range, from..to})
- end
-
- @impl true
- def handle_cast({:add_range, range}, %{processed_ranges: processed_ranges} = state) do
- ranges =
- [range | processed_ranges]
- |> Enum.sort_by(& &1.last, &>=/2)
- |> normalize_ranges()
-
- {new_last_number, new_ranges} = maybe_update_last_processed_number(state.last_processed_block_number, ranges)
-
- {:noreply, %{last_processed_block_number: new_last_number, processed_ranges: new_ranges}}
- end
-
- defp normalize_ranges(ranges) do
- %{prev_range: prev, result: result} =
- Enum.reduce(ranges, %{prev_range: nil, result: []}, fn range, %{prev_range: prev_range, result: result} ->
- case {prev_range, range} do
- {nil, _} ->
- %{prev_range: range, result: result}
-
- {%{last: l1} = r1, %{first: f2} = r2} when l1 - 1 > f2 ->
- %{prev_range: r2, result: [r1 | result]}
-
- {%{first: f1}, %{last: l2}} ->
- %{prev_range: f1..l2, result: result}
- end
- end)
-
- Enum.reverse([prev | result])
- end
-
- # since ranges are normalized, we need to check only the first range to determine the new last_processed_number
- defp maybe_update_last_processed_number(current_last, [from..to | rest] = ranges) when current_last - 1 <= from do
- case TokenTransferTokenIdMigratorProgress.update_last_processed_block_number(to) do
- {:ok, _} -> {to, rest}
- _ -> {current_last, ranges}
- end
- end
-
- defp maybe_update_last_processed_number(current_last, ranges), do: {current_last, ranges}
-end
diff --git a/apps/explorer/lib/explorer/token_transfer_token_id_migration/supervisor.ex b/apps/explorer/lib/explorer/token_transfer_token_id_migration/supervisor.ex
deleted file mode 100644
index 2121158fee35..000000000000
--- a/apps/explorer/lib/explorer/token_transfer_token_id_migration/supervisor.ex
+++ /dev/null
@@ -1,70 +0,0 @@
-defmodule Explorer.TokenTransferTokenIdMigration.Supervisor do
- @moduledoc """
- Supervises parts of token id migration process.
-
- Migration process algorithm:
-
- Defining the bounds of migration (by the first and the last block number of TokenTransfer).
- Supervisor starts the workers in amount equal to 'TOKEN_ID_MIGRATION_CONCURRENCY' env value (defaults to 1)
- and the 'LowestBlockNumberUpdater'.
-
- Each worker goes through the token transfers by batches ('TOKEN_ID_MIGRATION_BATCH_SIZE', defaults to 500)
- and updates the token_ids to be equal of [token_id] for transfers that has any token_id.
- Worker goes from the newest blocks to latest.
- After worker is done with current batch, it sends the information about processed batch to 'LowestBlockNumberUpdater'
- and takes the next by defining its bounds based on amount of all workers.
-
- For example, if batch size is 10, we have 5 workers and 100 items to be processed,
- the distribution will be like this:
- 1 worker - 99..90, 49..40
- 2 worker - 89..80, 39..30
- 3 worker - 79..70, 29..20
- 4 worker - 69..60, 19..10
- 5 worker - 59..50, 9..0
-
- 'LowestBlockNumberUpdater' keeps the information about the last processed block number
- (which is stored in the 'token_transfer_token_id_migrator_progress' db entity)
- and block ranges that has already been processed by the workers but couldn't be committed
- to last processed block number yet (because of the possible gap between the current last block
- and upper bound of the last processed batch). Uncommitted block numbers are stored in normalize ranges.
- When there is no gap between the last processed block number and the upper bound of the upper range,
- 'LowestBlockNumberUpdater' updates the last processed block number in db and drops this range from its state.
-
- This supervisor won't start if the migration is completed
- (last processed block number in db == 'TOKEN_ID_MIGRATION_FIRST_BLOCK' (defaults to 0)).
- """
- use Supervisor
-
- alias Explorer.TokenTransferTokenIdMigration.{LowestBlockNumberUpdater, Worker}
- alias Explorer.Utility.TokenTransferTokenIdMigratorProgress
-
- @default_first_block 0
- @default_workers_count 1
-
- def start_link(_) do
- Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)
- end
-
- @impl true
- def init(_) do
- first_block = Application.get_env(:explorer, :token_id_migration)[:first_block] || @default_first_block
- last_block = TokenTransferTokenIdMigratorProgress.get_last_processed_block_number()
-
- if last_block > first_block do
- workers_count = Application.get_env(:explorer, :token_id_migration)[:concurrency] || @default_workers_count
-
- workers =
- Enum.map(1..workers_count, fn id ->
- Supervisor.child_spec(
- {Worker, idx: id, first_block: first_block, last_block: last_block, step: workers_count - 1},
- id: {Worker, id},
- restart: :transient
- )
- end)
-
- Supervisor.init([LowestBlockNumberUpdater | workers], strategy: :one_for_one)
- else
- :ignore
- end
- end
-end
diff --git a/apps/explorer/lib/explorer/token_transfer_token_id_migration/worker.ex b/apps/explorer/lib/explorer/token_transfer_token_id_migration/worker.ex
deleted file mode 100644
index f7916ed0582a..000000000000
--- a/apps/explorer/lib/explorer/token_transfer_token_id_migration/worker.ex
+++ /dev/null
@@ -1,84 +0,0 @@
-defmodule Explorer.TokenTransferTokenIdMigration.Worker do
- @moduledoc """
- Performs the migration of TokenTransfer token_id to token_ids by batches.
- Full algorithm is in the 'Explorer.TokenTransferTokenIdMigration.Supervisor' module doc.
- """
- use GenServer
-
- import Ecto.Query
-
- alias Explorer.Chain.TokenTransfer
- alias Explorer.Repo
- alias Explorer.TokenTransferTokenIdMigration.LowestBlockNumberUpdater
-
- @default_batch_size 500
- @interval 10
-
- def start_link(idx: idx, first_block: first, last_block: last, step: step) do
- GenServer.start_link(__MODULE__, %{idx: idx, bottom_block: first, last_block: last, step: step})
- end
-
- @impl true
- def init(%{idx: idx, bottom_block: bottom_block, last_block: last_block, step: step}) do
- batch_size = Application.get_env(:explorer, :token_id_migration)[:batch_size] || @default_batch_size
- range = calculate_new_range(last_block, bottom_block, batch_size, idx - 1)
-
- schedule_next_update()
-
- {:ok, %{batch_size: batch_size, bottom_block: bottom_block, step: step, current_range: range}}
- end
-
- @impl true
- def handle_info(:update, %{current_range: :out_of_bound} = state) do
- {:stop, :normal, state}
- end
-
- @impl true
- def handle_info(:update, %{current_range: {lower_bound, upper_bound}} = state) do
- case do_update(lower_bound, upper_bound) do
- true ->
- LowestBlockNumberUpdater.add_range(upper_bound, lower_bound)
- new_range = calculate_new_range(lower_bound, state.bottom_block, state.batch_size, state.step)
- schedule_next_update()
- {:noreply, %{state | current_range: new_range}}
-
- _ ->
- schedule_next_update()
- {:noreply, state}
- end
- end
-
- defp calculate_new_range(last_processed_block, bottom_block, batch_size, step) do
- upper_bound = last_processed_block - step * batch_size - 1
- lower_bound = max(upper_bound - batch_size + 1, bottom_block)
-
- if upper_bound >= bottom_block do
- {lower_bound, upper_bound}
- else
- :out_of_bound
- end
- end
-
- defp do_update(lower_bound, upper_bound) do
- token_transfers_batch_query =
- from(
- tt in TokenTransfer,
- where: tt.block_number >= ^lower_bound,
- where: tt.block_number <= ^upper_bound
- )
-
- token_transfers_batch_query
- |> Repo.all()
- |> Enum.filter(fn %{token_id: token_id} -> not is_nil(token_id) end)
- |> Enum.map(fn token_transfer ->
- token_transfer
- |> TokenTransfer.changeset(%{token_ids: [token_transfer.token_id], token_id: nil})
- |> Repo.update()
- end)
- |> Enum.all?(&match?({:ok, _}, &1))
- end
-
- defp schedule_next_update do
- Process.send_after(self(), :update, @interval)
- end
-end
diff --git a/apps/explorer/lib/explorer/utility/microservice.ex b/apps/explorer/lib/explorer/utility/microservice.ex
new file mode 100644
index 000000000000..fe1af3df46b4
--- /dev/null
+++ b/apps/explorer/lib/explorer/utility/microservice.ex
@@ -0,0 +1,27 @@
+defmodule Explorer.Utility.Microservice do
+ @moduledoc """
+ Module is responsible for common utils related to microservices.
+ """
+ def base_url(application \\ :explorer, module) do
+ url = Application.get_env(application, module)[:service_url]
+
+ if String.ends_with?(url, "/") do
+ url
+ |> String.slice(0..(String.length(url) - 2))
+ else
+ url
+ end
+ end
+
+ @doc """
+ Returns :ok if Application.get_env(:explorer, module)[:enabled] is true (module is enabled)
+ """
+ @spec check_enabled(atom) :: :ok | {:error, :disabled}
+ def check_enabled(module) do
+ if Application.get_env(:explorer, module)[:enabled] do
+ :ok
+ else
+ {:error, :disabled}
+ end
+ end
+end
diff --git a/apps/explorer/lib/explorer/utility/missing_block_range.ex b/apps/explorer/lib/explorer/utility/missing_block_range.ex
index 0d5d8bd3fca2..4892334d5fd5 100644
--- a/apps/explorer/lib/explorer/utility/missing_block_range.ex
+++ b/apps/explorer/lib/explorer/utility/missing_block_range.ex
@@ -57,10 +57,11 @@ defmodule Explorer.Utility.MissingBlockRange do
update_range(range, %{to_number: min_number})
{%__MODULE__{} = range_1, %__MODULE__{} = range_2} ->
- delete_ranges_between(range_2.from_number, range_1.from_number)
+ delete_ranges_between(range_2.from_number + 1, range_1.from_number)
update_range(range_1, %{from_number: range_2.from_number})
_ ->
+ delete_ranges_between(max_number, min_number)
insert_range(%{from_number: max_number, to_number: min_number})
end
end
@@ -92,7 +93,7 @@ defmodule Explorer.Utility.MissingBlockRange do
update_to_number_or_delete_range(range_2, max_number + 1)
_ ->
- :ok
+ delete_ranges_between(max_number, min_number)
end
end
diff --git a/apps/explorer/lib/explorer/utility/rust_service.ex b/apps/explorer/lib/explorer/utility/rust_service.ex
deleted file mode 100644
index 63f949961252..000000000000
--- a/apps/explorer/lib/explorer/utility/rust_service.ex
+++ /dev/null
@@ -1,15 +0,0 @@
-defmodule Explorer.Utility.RustService do
- @moduledoc """
- Module is responsible for common utils related to rust microservices.
- """
- def base_url(module) do
- url = Application.get_env(:explorer, module)[:service_url]
-
- if String.ends_with?(url, "/") do
- url
- |> String.slice(0..(String.length(url) - 2))
- else
- url
- end
- end
-end
diff --git a/apps/explorer/lib/explorer/utility/token_transfer_token_id_migrator_progress.ex b/apps/explorer/lib/explorer/utility/token_transfer_token_id_migrator_progress.ex
deleted file mode 100644
index 3a28181016e5..000000000000
--- a/apps/explorer/lib/explorer/utility/token_transfer_token_id_migrator_progress.ex
+++ /dev/null
@@ -1,67 +0,0 @@
-defmodule Explorer.Utility.TokenTransferTokenIdMigratorProgress do
- @moduledoc """
- Module is responsible for keeping the current progress of TokenTransfer token_id migration.
- Full algorithm is in the 'Indexer.Fetcher.TokenTransferTokenIdMigration.Supervisor' module doc.
- """
- use Explorer.Schema
-
- require Logger
-
- alias Explorer.Chain.Cache.BlockNumber
- alias Explorer.Repo
-
- schema "token_transfer_token_id_migrator_progress" do
- field(:last_processed_block_number, :integer)
-
- timestamps()
- end
-
- @doc false
- def changeset(progress \\ %__MODULE__{}, params) do
- cast(progress, params, [:last_processed_block_number])
- end
-
- def get_current_progress do
- Repo.one(
- from(
- p in __MODULE__,
- order_by: [desc: p.updated_at],
- limit: 1
- )
- )
- end
-
- def get_last_processed_block_number do
- case get_current_progress() do
- nil ->
- latest_processed_block_number = BlockNumber.get_max() + 1
- update_last_processed_block_number(latest_processed_block_number)
- latest_processed_block_number
-
- %{last_processed_block_number: block_number} ->
- block_number
- end
- end
-
- def update_last_processed_block_number(block_number, force \\ false) do
- case get_current_progress() do
- nil ->
- %{last_processed_block_number: block_number}
- |> changeset()
- |> Repo.insert()
-
- progress ->
- if not force and progress.last_processed_block_number < block_number do
- Logger.error(
- "TokenTransferTokenIdMigratorProgress new block_number is above the last one. Last: #{progress.last_processed_block_number}, new: #{block_number}"
- )
-
- {:error, :invalid_block_number}
- else
- progress
- |> changeset(%{last_processed_block_number: block_number})
- |> Repo.update()
- end
- end
- end
-end
diff --git a/apps/explorer/lib/explorer/validator/metadata_retriever.ex b/apps/explorer/lib/explorer/validator/metadata_retriever.ex
index 6978bab0e032..10794167b13c 100644
--- a/apps/explorer/lib/explorer/validator/metadata_retriever.ex
+++ b/apps/explorer/lib/explorer/validator/metadata_retriever.ex
@@ -16,16 +16,26 @@ defmodule Explorer.Validator.MetadataRetriever do
end
def fetch_validators_list do
+ validators_contract_address = config(:validators_contract_address)
+
+ validators_contract_address_checked =
+ if is_nil(validators_contract_address) and Mix.env() == :test do
+ "0x0000000000000000000000000000000000006001"
+ else
+ validators_contract_address
+ end
+
# b7ab4db5 = keccak256(getValidators())
- case Reader.query_contract(
- config(:validators_contract_address),
- contract_abi("validators.json"),
- %{
- "b7ab4db5" => []
- },
- false
- ) do
- %{"b7ab4db5" => {:ok, [validators]}} -> validators
+ with false <- is_nil(validators_contract_address_checked),
+ %{"b7ab4db5" => {:ok, [validators]}} <-
+ Reader.query_contract(
+ validators_contract_address_checked,
+ contract_abi("validators.json"),
+ %{"b7ab4db5" => []},
+ false
+ ) do
+ validators
+ else
_ -> []
end
end
diff --git a/apps/explorer/lib/explorer/visualize/sol2uml.ex b/apps/explorer/lib/explorer/visualize/sol2uml.ex
index 10a6c2a9efda..459531a36299 100644
--- a/apps/explorer/lib/explorer/visualize/sol2uml.ex
+++ b/apps/explorer/lib/explorer/visualize/sol2uml.ex
@@ -2,7 +2,7 @@ defmodule Explorer.Visualize.Sol2uml do
@moduledoc """
Adapter for sol2uml visualizer with https://github.com/blockscout/blockscout-rs/blob/main/visualizer
"""
- alias Explorer.Utility.RustService
+ alias Explorer.Utility.Microservice
alias HTTPoison.Response
require Logger
@@ -61,7 +61,7 @@ defmodule Explorer.Visualize.Sol2uml do
def base_api_url, do: "#{base_url()}" <> "/api/v1"
def base_url do
- RustService.base_url(__MODULE__)
+ Microservice.base_url(__MODULE__)
end
def enabled?, do: Application.get_env(:explorer, __MODULE__)[:enabled]
diff --git a/apps/explorer/lib/release_tasks.ex b/apps/explorer/lib/release_tasks.ex
index 1fcdc7fca8c3..a55174738421 100644
--- a/apps/explorer/lib/release_tasks.ex
+++ b/apps/explorer/lib/release_tasks.ex
@@ -14,7 +14,9 @@ defmodule Explorer.ReleaseTasks do
:ecto_sql
]
- @repos Application.compile_env(:blockscout, :ecto_repos, [Explorer.Repo, Explorer.Repo.Account])
+ def repos do
+ Application.get_env(:explorer, :ecto_repos)
+ end
def create_and_migrate do
start_services()
@@ -26,7 +28,7 @@ defmodule Explorer.ReleaseTasks do
end
def create do
- Enum.each(@repos, &create_db_for/1)
+ Enum.each(repos(), &create_db_for/1)
end
def migrate(_argv) do
@@ -56,7 +58,7 @@ defmodule Explorer.ReleaseTasks do
IO.puts("Starting repos..")
# Switch pool_size to 2 for ecto > 3.0
- Enum.each(@repos, & &1.start_link(pool_size: 2))
+ Enum.each(repos(), & &1.start_link(pool_size: 2))
end
defp stop_services do
@@ -75,7 +77,7 @@ defmodule Explorer.ReleaseTasks do
end
defp run_migrations do
- Enum.each(@repos, &run_migrations_for/1)
+ Enum.each(repos(), &run_migrations_for/1)
end
defp run_migrations_for(repo) do
@@ -86,7 +88,7 @@ defmodule Explorer.ReleaseTasks do
end
defp run_seeds do
- Enum.each(@repos, &run_seeds_for/1)
+ Enum.each(repos(), &run_seeds_for/1)
end
# sobelow_skip ["RCE.CodeModule"]
diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs
index 5e7b639da343..5f98f045ff91 100644
--- a/apps/explorer/mix.exs
+++ b/apps/explorer/mix.exs
@@ -11,7 +11,7 @@ defmodule Explorer.Mixfile do
deps_path: "../../deps",
description: "Read-access to indexed block chain data.",
dialyzer: [
- plt_add_deps: :transitive,
+ plt_add_deps: :app_tree,
plt_add_apps: ~w(ex_unit mix)a,
ignore_warnings: "../../.dialyzer-ignore"
],
@@ -24,7 +24,7 @@ defmodule Explorer.Mixfile do
dialyzer: :test
],
start_permanent: Mix.env() == :prod,
- version: "5.1.5",
+ version: "6.0.0",
xref: [exclude: [BlockScoutWeb.WebRouter.Helpers]]
]
end
@@ -61,7 +61,7 @@ defmodule Explorer.Mixfile do
{:mime, "~> 2.0"},
{:bcrypt_elixir, "~> 3.0"},
# benchmark optimizations
- {:benchee, "~> 1.1.0", only: :test},
+ {:benchee, "~> 1.3.0", only: :test},
# CSV output for benchee
{:benchee_csv, "~> 1.0.0", only: :test},
{:bypass, "~> 2.1", only: :test},
@@ -113,11 +113,12 @@ defmodule Explorer.Mixfile do
# `Timex.Duration` for `Explorer.Counters.AverageBlockTime.average_block_time/0`
{:timex, "~> 3.7.1"},
{:con_cache, "~> 1.0"},
- {:tesla, "~> 1.7.0"},
+ {:tesla, "~> 1.8.0"},
{:cbor, "~> 1.0"},
{:cloak_ecto, "~> 1.2.0"},
{:redix, "~> 1.1"},
- {:hammer_backend_redis, "~> 6.1"}
+ {:hammer_backend_redis, "~> 6.1"},
+ {:logger_json, "~> 5.1"}
]
end
diff --git a/apps/explorer/package-lock.json b/apps/explorer/package-lock.json
index 4f33855a6d04..88f5dd663846 100644
--- a/apps/explorer/package-lock.json
+++ b/apps/explorer/package-lock.json
@@ -7,7 +7,7 @@
"name": "blockscout",
"license": "GPL-3.0",
"dependencies": {
- "solc": "0.8.20"
+ "solc": "0.8.23"
},
"engines": {
"node": "18.x",
@@ -28,9 +28,9 @@
}
},
"node_modules/follow-redirects": {
- "version": "1.14.8",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz",
- "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==",
+ "version": "1.15.4",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
+ "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"funding": [
{
"type": "individual",
@@ -59,6 +59,20 @@
"node": ">= 0.10.0"
}
},
+ "node_modules/n": {
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/n/-/n-9.2.0.tgz",
+ "integrity": "sha512-R8mFN2OWwNVc+r1f9fDzcT34DnDwUIHskrpTesZ6SdluaXBBnRtTu5tlfaSPloBi1Z/eGJoPO9nhyawWPad5UQ==",
+ "os": [
+ "!win32"
+ ],
+ "bin": {
+ "n": "bin/n"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
@@ -68,23 +82,24 @@
}
},
"node_modules/semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"bin": {
"semver": "bin/semver"
}
},
"node_modules/solc": {
- "version": "0.8.20",
- "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.20.tgz",
- "integrity": "sha512-fPRnGspIEqmhu63RFO3pc79sLA7ZmzO0Uy0L5l6hEt2wAsq0o7UV6pXkAp3Mfv9IBhg7Px/oTu3a+y4gs3BWrQ==",
+ "version": "0.8.23",
+ "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.23.tgz",
+ "integrity": "sha512-uqe69kFWfJc3cKdxj+Eg9CdW1CP3PLZDPeyJStQVWL8Q9jjjKD0VuRAKBFR8mrWiq5A7gJqERxJFYJsklrVsfA==",
"dependencies": {
"command-exists": "^1.2.8",
"commander": "^8.1.0",
"follow-redirects": "^1.12.1",
"js-sha3": "0.8.0",
"memorystream": "^0.3.1",
+ "n": "^9.2.0",
"semver": "^5.5.0",
"tmp": "0.0.33"
},
@@ -119,9 +134,9 @@
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
},
"follow-redirects": {
- "version": "1.14.8",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz",
- "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA=="
+ "version": "1.15.4",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
+ "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw=="
},
"js-sha3": {
"version": "0.8.0",
@@ -133,26 +148,32 @@
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
"integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI="
},
+ "n": {
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/n/-/n-9.2.0.tgz",
+ "integrity": "sha512-R8mFN2OWwNVc+r1f9fDzcT34DnDwUIHskrpTesZ6SdluaXBBnRtTu5tlfaSPloBi1Z/eGJoPO9nhyawWPad5UQ=="
+ },
"os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
},
"semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="
},
"solc": {
- "version": "0.8.20",
- "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.20.tgz",
- "integrity": "sha512-fPRnGspIEqmhu63RFO3pc79sLA7ZmzO0Uy0L5l6hEt2wAsq0o7UV6pXkAp3Mfv9IBhg7Px/oTu3a+y4gs3BWrQ==",
+ "version": "0.8.23",
+ "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.23.tgz",
+ "integrity": "sha512-uqe69kFWfJc3cKdxj+Eg9CdW1CP3PLZDPeyJStQVWL8Q9jjjKD0VuRAKBFR8mrWiq5A7gJqERxJFYJsklrVsfA==",
"requires": {
"command-exists": "^1.2.8",
"commander": "^8.1.0",
"follow-redirects": "^1.12.1",
"js-sha3": "0.8.0",
"memorystream": "^0.3.1",
+ "n": "^9.2.0",
"semver": "^5.5.0",
"tmp": "0.0.33"
}
diff --git a/apps/explorer/package.json b/apps/explorer/package.json
index 0a0f2e851d9d..01333da43558 100644
--- a/apps/explorer/package.json
+++ b/apps/explorer/package.json
@@ -13,6 +13,6 @@
},
"scripts": {},
"dependencies": {
- "solc": "0.8.20"
+ "solc": "0.8.23"
}
}
diff --git a/apps/explorer/priv/account/migrations/20231207201701_add_watchlist_id_column.exs b/apps/explorer/priv/account/migrations/20231207201701_add_watchlist_id_column.exs
new file mode 100644
index 000000000000..346c9ec05ee8
--- /dev/null
+++ b/apps/explorer/priv/account/migrations/20231207201701_add_watchlist_id_column.exs
@@ -0,0 +1,23 @@
+defmodule Explorer.Repo.Account.Migrations.AddWatchlistIdColumn do
+ use Ecto.Migration
+
+ def change do
+ execute("""
+ ALTER TABLE public.account_watchlist_notifications
+ DROP CONSTRAINT account_watchlist_notifications_watchlist_address_id_fkey;
+ """)
+
+ alter table(:account_watchlist_notifications) do
+ add(:watchlist_id, :bigserial)
+ end
+
+ create(index(:account_watchlist_notifications, [:watchlist_id]))
+
+ execute("""
+ UPDATE account_watchlist_notifications awn
+ SET watchlist_id = awa.watchlist_id
+ FROM account_watchlist_addresses awa
+ WHERE awa.id = awn.watchlist_address_id
+ """)
+ end
+end
diff --git a/apps/explorer/priv/polygon_edge/migrations/20230618132249_create_polygon_edge_withdrawal_tables.exs b/apps/explorer/priv/polygon_edge/migrations/20230618132249_create_polygon_edge_withdrawal_tables.exs
new file mode 100644
index 000000000000..b69751a14a2b
--- /dev/null
+++ b/apps/explorer/priv/polygon_edge/migrations/20230618132249_create_polygon_edge_withdrawal_tables.exs
@@ -0,0 +1,28 @@
+defmodule Explorer.Repo.PolygonEdge.Migrations.CreatePolygonEdgeWithdrawalTables do
+ use Ecto.Migration
+
+ def change do
+ create table(:polygon_edge_withdrawals, primary_key: false) do
+ add(:msg_id, :bigint, null: false, primary_key: true)
+ add(:from, :bytea, null: true)
+ add(:to, :bytea, null: true)
+ add(:l2_transaction_hash, :bytea, null: false)
+ add(:l2_block_number, :bigint, null: false)
+
+ timestamps(null: false, type: :utc_datetime_usec)
+ end
+
+ create table(:polygon_edge_withdrawal_exits, primary_key: false) do
+ add(:msg_id, :bigint, null: false, primary_key: true)
+ add(:l1_transaction_hash, :bytea, null: false)
+ add(:l1_block_number, :bigint, null: false)
+ add(:success, :boolean, null: false)
+
+ timestamps(null: false, type: :utc_datetime_usec)
+ end
+
+ create(unique_index(:polygon_edge_withdrawals, :l2_transaction_hash))
+ create(index(:polygon_edge_withdrawals, :l2_block_number))
+ create(index(:polygon_edge_withdrawal_exits, :l1_block_number))
+ end
+end
diff --git a/apps/explorer/priv/polygon_edge/migrations/20230707113550_create_polygon_edge_deposit_tables.exs b/apps/explorer/priv/polygon_edge/migrations/20230707113550_create_polygon_edge_deposit_tables.exs
new file mode 100644
index 000000000000..36ef8613b2e5
--- /dev/null
+++ b/apps/explorer/priv/polygon_edge/migrations/20230707113550_create_polygon_edge_deposit_tables.exs
@@ -0,0 +1,29 @@
+defmodule Explorer.Repo.PolygonEdge.Migrations.CreatePolygonEdgeDepositTables do
+ use Ecto.Migration
+
+ def change do
+ create table(:polygon_edge_deposits, primary_key: false) do
+ add(:msg_id, :bigint, null: false, primary_key: true)
+ add(:from, :bytea, null: true)
+ add(:to, :bytea, null: true)
+ add(:l1_transaction_hash, :bytea, null: false)
+ add(:l1_block_number, :bigint, null: false)
+ add(:l1_timestamp, :"timestamp without time zone", null: true)
+
+ timestamps(null: false, type: :utc_datetime_usec)
+ end
+
+ create table(:polygon_edge_deposit_executes, primary_key: false) do
+ add(:msg_id, :bigint, null: false, primary_key: true)
+ add(:l2_transaction_hash, :bytea, null: false)
+ add(:l2_block_number, :bigint, null: false)
+ add(:success, :boolean, null: false)
+
+ timestamps(null: false, type: :utc_datetime_usec)
+ end
+
+ create(unique_index(:polygon_edge_deposit_executes, :l2_transaction_hash))
+ create(index(:polygon_edge_deposits, :l1_block_number))
+ create(index(:polygon_edge_deposit_executes, :l2_block_number))
+ end
+end
diff --git a/apps/explorer/priv/polygon_edge/migrations/20230731130103_modify_collated_gas_price_constraint.exs b/apps/explorer/priv/polygon_edge/migrations/20230731130103_modify_collated_gas_price_constraint.exs
new file mode 100644
index 000000000000..559799e0dee2
--- /dev/null
+++ b/apps/explorer/priv/polygon_edge/migrations/20230731130103_modify_collated_gas_price_constraint.exs
@@ -0,0 +1,15 @@
+defmodule Explorer.Repo.Migrations.ModifyCollatedGasPriceConstraint do
+ use Ecto.Migration
+
+ def change do
+ execute("ALTER TABLE transactions DROP CONSTRAINT collated_gas_price")
+
+ create(
+ constraint(
+ :transactions,
+ :collated_gas_price,
+ check: "block_hash IS NULL OR gas_price IS NOT NULL OR max_fee_per_gas IS NOT NULL"
+ )
+ )
+ end
+end
diff --git a/apps/explorer/priv/polygon_zkevm/migrations/20230420110800_create_zkevm_tables.exs b/apps/explorer/priv/polygon_zkevm/migrations/20230420110800_create_zkevm_tables.exs
new file mode 100644
index 000000000000..1c242e3b8ec4
--- /dev/null
+++ b/apps/explorer/priv/polygon_zkevm/migrations/20230420110800_create_zkevm_tables.exs
@@ -0,0 +1,55 @@
+defmodule Explorer.Repo.PolygonZkevm.Migrations.CreateZkevmTables do
+ use Ecto.Migration
+
+ def change do
+ create table(:zkevm_lifecycle_l1_transactions, primary_key: false) do
+ add(:id, :integer, null: false, primary_key: true)
+ add(:hash, :bytea, null: false)
+ add(:is_verify, :boolean, null: false)
+ timestamps(null: false, type: :utc_datetime_usec)
+ end
+
+ create(unique_index(:zkevm_lifecycle_l1_transactions, :hash))
+
+ create table(:zkevm_transaction_batches, primary_key: false) do
+ add(:number, :integer, null: false, primary_key: true)
+ add(:timestamp, :"timestamp without time zone", null: false)
+ add(:l2_transactions_count, :integer, null: false)
+ add(:global_exit_root, :bytea, null: false)
+ add(:acc_input_hash, :bytea, null: false)
+ add(:state_root, :bytea, null: false)
+
+ add(
+ :sequence_id,
+ references(:zkevm_lifecycle_l1_transactions, on_delete: :restrict, on_update: :update_all, type: :integer),
+ null: true
+ )
+
+ add(
+ :verify_id,
+ references(:zkevm_lifecycle_l1_transactions, on_delete: :restrict, on_update: :update_all, type: :integer),
+ null: true
+ )
+
+ timestamps(null: false, type: :utc_datetime_usec)
+ end
+
+ create table(:zkevm_batch_l2_transactions, primary_key: false) do
+ add(
+ :batch_number,
+ references(:zkevm_transaction_batches,
+ column: :number,
+ on_delete: :delete_all,
+ on_update: :update_all,
+ type: :integer
+ ),
+ null: false
+ )
+
+ add(:hash, :bytea, null: false, primary_key: true)
+ timestamps(null: false, type: :utc_datetime_usec)
+ end
+
+ create(index(:zkevm_batch_l2_transactions, :batch_number))
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20230522130735_withdrawals_gwei_amount_to_wei_amount.exs b/apps/explorer/priv/repo/migrations/20230522130735_withdrawals_gwei_amount_to_wei_amount.exs
new file mode 100644
index 000000000000..e18bf417516e
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20230522130735_withdrawals_gwei_amount_to_wei_amount.exs
@@ -0,0 +1,7 @@
+defmodule Explorer.Repo.Migrations.WithdrawalsGweiAmountToWeiAmount do
+ use Ecto.Migration
+
+ def change do
+ execute("UPDATE withdrawals SET amount = amount * 1000000000")
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20230530074105_market_history_add_market_cap.exs b/apps/explorer/priv/repo/migrations/20230530074105_market_history_add_market_cap.exs
new file mode 100644
index 000000000000..b0ddaca92c21
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20230530074105_market_history_add_market_cap.exs
@@ -0,0 +1,9 @@
+defmodule Explorer.Repo.Migrations.MarketHistoryAddMarketCap do
+ use Ecto.Migration
+
+ def change do
+ alter table(:market_history) do
+ add(:market_cap, :decimal)
+ end
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20230605080138_add_verified_via_eth_bytecode_db.exs b/apps/explorer/priv/repo/migrations/20230605080138_add_verified_via_eth_bytecode_db.exs
new file mode 100644
index 000000000000..9cef42d46938
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20230605080138_add_verified_via_eth_bytecode_db.exs
@@ -0,0 +1,9 @@
+defmodule Explorer.Repo.Migrations.AddVerifiedViaEthBytecodeDb do
+ use Ecto.Migration
+
+ def change do
+ alter table(:smart_contracts) do
+ add(:verified_via_eth_bytecode_db, :boolean, null: true)
+ end
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20230606091935_fix_contract_creation_transactions.exs b/apps/explorer/priv/repo/migrations/20230606091935_fix_contract_creation_transactions.exs
new file mode 100644
index 000000000000..86a088ae50b7
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20230606091935_fix_contract_creation_transactions.exs
@@ -0,0 +1,18 @@
+defmodule Explorer.Repo.Migrations.FixContractCreationTransactions do
+ use Ecto.Migration
+
+ def change do
+ execute("""
+ UPDATE addresses a
+ SET contract_code = NULL
+ FROM transactions t
+ WHERE t.created_contract_address_hash IS NOT NULL AND t.created_contract_address_hash = a.hash AND t.to_address_hash IS NOT NULL;
+ """)
+
+ execute("""
+ UPDATE transactions
+ SET created_contract_address_hash = NULL
+ WHERE created_contract_address_hash IS NOT NULL AND to_address_hash IS NOT NULL;
+ """)
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20230613181244_address_coin_balances_block_number_index.exs b/apps/explorer/priv/repo/migrations/20230613181244_address_coin_balances_block_number_index.exs
new file mode 100644
index 000000000000..f50a577455a3
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20230613181244_address_coin_balances_block_number_index.exs
@@ -0,0 +1,11 @@
+defmodule Explorer.Repo.Migrations.AddressCoinBalancesBlockNumberIndex do
+ use Ecto.Migration
+
+ def up do
+ create(index(:address_coin_balances, [:block_number]))
+ end
+
+ def down do
+ drop(index(:address_coin_balances, [:block_number]))
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20230615130940_add_blocks_date_index.exs b/apps/explorer/priv/repo/migrations/20230615130940_add_blocks_date_index.exs
new file mode 100644
index 000000000000..c0b415d676e4
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20230615130940_add_blocks_date_index.exs
@@ -0,0 +1,11 @@
+defmodule Explorer.Repo.Migrations.AddBlocksDateIndex do
+ use Ecto.Migration
+
+ def up do
+ execute("CREATE INDEX IF NOT EXISTS blocks_date ON blocks(date(timestamp), number);")
+ end
+
+ def down do
+ execute("DROP INDEX IF EXISTS blocks_date;")
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20230719160318_delete_erc_1155_tt_with_empty_token_ids.exs b/apps/explorer/priv/repo/migrations/20230719160318_delete_erc_1155_tt_with_empty_token_ids.exs
new file mode 100644
index 000000000000..190fdf8e8023
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20230719160318_delete_erc_1155_tt_with_empty_token_ids.exs
@@ -0,0 +1,9 @@
+defmodule Explorer.Repo.Migrations.DeleteErc1155TtWithEmptyTokenIds do
+ use Ecto.Migration
+
+ def change do
+ execute("""
+ DELETE from token_transfers USING tokens WHERE token_transfers.token_contract_address_hash = tokens.contract_address_hash AND tokens.type = 'ERC-1155' AND (token_transfers.token_ids IS NULL OR ARRAY_LENGTH(token_transfers.token_ids, 1) = 0) ;
+ """)
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20230809134253_add_is_verified_via_admin_panel.exs b/apps/explorer/priv/repo/migrations/20230809134253_add_is_verified_via_admin_panel.exs
new file mode 100644
index 000000000000..1cfa57cb6fca
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20230809134253_add_is_verified_via_admin_panel.exs
@@ -0,0 +1,9 @@
+defmodule Explorer.Repo.Migrations.AddIsVerifiedViaAdminPanel do
+ use Ecto.Migration
+
+ def change do
+ alter table(:tokens) do
+ add(:is_verified_via_admin_panel, :boolean, null: true, default: false)
+ end
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20230815131151_drop_logs_address_hash_foreign_key.exs b/apps/explorer/priv/repo/migrations/20230815131151_drop_logs_address_hash_foreign_key.exs
new file mode 100644
index 000000000000..e847cb14d74f
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20230815131151_drop_logs_address_hash_foreign_key.exs
@@ -0,0 +1,8 @@
+# cspell:ignore fkey
+defmodule Explorer.Repo.Migrations.DropLogsAddressHashForeignKey do
+ use Ecto.Migration
+
+ def change do
+ drop_if_exists(constraint(:logs, :logs_address_hash_fkey))
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20230816061723_drop_token_transfers_and_transactions_address_foreign_key.exs b/apps/explorer/priv/repo/migrations/20230816061723_drop_token_transfers_and_transactions_address_foreign_key.exs
new file mode 100644
index 000000000000..1cfe0a5fb3d7
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20230816061723_drop_token_transfers_and_transactions_address_foreign_key.exs
@@ -0,0 +1,13 @@
+# cspell:ignore fkey
+defmodule Explorer.Repo.Migrations.DropTokenTransfersAndTransactionsAddressForeignKey do
+ use Ecto.Migration
+
+ def change do
+ drop_if_exists(constraint(:token_transfers, :token_transfers_from_address_hash_fkey))
+ drop_if_exists(constraint(:token_transfers, :token_transfers_to_address_hash_fkey))
+ drop_if_exists(constraint(:token_transfers, :token_transfers_token_contract_address_hash_fkey))
+ drop_if_exists(constraint(:transactions, :transactions_created_contract_address_hash_fkey))
+ drop_if_exists(constraint(:transactions, :transactions_from_address_hash_fkey))
+ drop_if_exists(constraint(:transactions, :transactions_to_address_hash_fkey))
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20230817061317_drop_address_foreign_keys.exs b/apps/explorer/priv/repo/migrations/20230817061317_drop_address_foreign_keys.exs
new file mode 100644
index 000000000000..f10c301b76e3
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20230817061317_drop_address_foreign_keys.exs
@@ -0,0 +1,14 @@
+# cspell:ignore fkey
+defmodule Explorer.Repo.Migrations.DropAddressForeignKeys do
+ use Ecto.Migration
+
+ def change do
+ drop_if_exists(constraint(:address_coin_balances, :address_coin_balances_address_hash_fkey))
+ drop_if_exists(constraint(:address_token_balances, :address_token_balances_address_hash_fkey))
+ drop_if_exists(constraint(:address_current_token_balances, :address_current_token_balances_address_hash_fkey))
+ drop_if_exists(constraint(:tokens, :tokens_contract_address_hash_fkey))
+ drop_if_exists(constraint(:internal_transactions, :internal_transactions_created_contract_address_hash_fkey))
+ drop_if_exists(constraint(:internal_transactions, :internal_transactions_from_address_hash_fkey))
+ drop_if_exists(constraint(:internal_transactions, :internal_transactions_to_address_hash_fkey))
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20230818094455_add_token_ids_to_address_token_balances.exs b/apps/explorer/priv/repo/migrations/20230818094455_add_token_ids_to_address_token_balances.exs
new file mode 100644
index 000000000000..a141276ab529
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20230818094455_add_token_ids_to_address_token_balances.exs
@@ -0,0 +1,13 @@
+defmodule Explorer.Repo.Migrations.AddTokenIdsToAddressTokenBalances do
+ use Ecto.Migration
+
+ def change do
+ alter table(:token_instances) do
+ add(:owner_address_hash, :bytea, null: true)
+ add(:owner_updated_at_block, :bigint, null: true)
+ add(:owner_updated_at_log_index, :integer, null: true)
+ end
+
+ create(index(:token_instances, [:owner_address_hash]))
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20230821120625_drop_rest_address_foreign_keys.exs b/apps/explorer/priv/repo/migrations/20230821120625_drop_rest_address_foreign_keys.exs
new file mode 100644
index 000000000000..36cdd2353fa5
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20230821120625_drop_rest_address_foreign_keys.exs
@@ -0,0 +1,14 @@
+# cspell:ignore fkey
+defmodule Explorer.Repo.Migrations.DropRestAddressForeignKeys do
+ use Ecto.Migration
+
+ def change do
+ drop_if_exists(constraint(:address_coin_balances_daily, :address_coin_balances_daily_address_hash_fkey))
+ drop_if_exists(constraint(:address_to_tags, :address_to_tags_address_hash_fkey))
+ drop_if_exists(constraint(:block_rewards, :block_rewards_address_hash_fkey))
+ drop_if_exists(constraint(:decompiled_smart_contracts, :decompiled_smart_contracts_address_hash_fkey))
+ drop_if_exists(constraint(:smart_contracts, :smart_contracts_address_hash_fkey))
+ drop_if_exists(constraint(:withdrawals, :withdrawals_address_hash_fkey))
+ drop_if_exists(constraint(:blocks, :blocks_miner_hash_fkey))
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20230831122819_drop_current_token_balances_tokens_foreign_key.exs b/apps/explorer/priv/repo/migrations/20230831122819_drop_current_token_balances_tokens_foreign_key.exs
new file mode 100644
index 000000000000..3befa9600c66
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20230831122819_drop_current_token_balances_tokens_foreign_key.exs
@@ -0,0 +1,10 @@
+# cspell:ignore fkey
+defmodule Explorer.Repo.Migrations.DropCurrentTokenBalancesTokensForeignKey do
+ use Ecto.Migration
+
+ def change do
+ drop_if_exists(
+ constraint(:address_current_token_balances, :address_current_token_balances_token_contract_address_hash_fkey)
+ )
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20230905085809_drop_token_balances_tokens_foreign_key.exs b/apps/explorer/priv/repo/migrations/20230905085809_drop_token_balances_tokens_foreign_key.exs
new file mode 100644
index 000000000000..265933ff4c23
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20230905085809_drop_token_balances_tokens_foreign_key.exs
@@ -0,0 +1,8 @@
+# cspell:ignore fkey
+defmodule Explorer.Repo.Migrations.DropTokenBalancesTokensForeignKey do
+ use Ecto.Migration
+
+ def change do
+ drop_if_exists(constraint(:address_token_balances, :address_token_balances_token_contract_address_hash_fkey))
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20231003093553_add_tvl_to_market_history_table.exs b/apps/explorer/priv/repo/migrations/20231003093553_add_tvl_to_market_history_table.exs
new file mode 100644
index 000000000000..5ff8dd795a3f
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20231003093553_add_tvl_to_market_history_table.exs
@@ -0,0 +1,9 @@
+defmodule Explorer.Repo.Migrations.AddTvlToMarketHistoryTable do
+ use Ecto.Migration
+
+ def change do
+ alter table(:market_history) do
+ add(:tvl, :decimal)
+ end
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20231212101547_add_block_timestamp_and_consensus_to_transactions.exs b/apps/explorer/priv/repo/migrations/20231212101547_add_block_timestamp_and_consensus_to_transactions.exs
new file mode 100644
index 000000000000..458e29604383
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20231212101547_add_block_timestamp_and_consensus_to_transactions.exs
@@ -0,0 +1,13 @@
+defmodule Explorer.Repo.Migrations.AddBlockTimestampAndConsensusToTransactions do
+ use Ecto.Migration
+
+ def change do
+ alter table(:transactions) do
+ add_if_not_exists(:block_timestamp, :utc_datetime_usec)
+ add_if_not_exists(:block_consensus, :boolean, default: true)
+ end
+
+ create_if_not_exists(index(:transactions, :block_timestamp))
+ create_if_not_exists(index(:transactions, :block_consensus))
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20231212102127_create_migrations_status.exs b/apps/explorer/priv/repo/migrations/20231212102127_create_migrations_status.exs
new file mode 100644
index 000000000000..0b8bf54a5c3b
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20231212102127_create_migrations_status.exs
@@ -0,0 +1,12 @@
+defmodule Explorer.Repo.Migrations.CreateMigrationsStatus do
+ use Ecto.Migration
+
+ def change do
+ create table(:migrations_status, primary_key: false) do
+ add(:migration_name, :string, primary_key: true)
+ add(:status, :string)
+
+ timestamps()
+ end
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20231213085254_add_btree_gin_extension.exs b/apps/explorer/priv/repo/migrations/20231213085254_add_btree_gin_extension.exs
new file mode 100644
index 000000000000..b34f9ee059cb
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20231213085254_add_btree_gin_extension.exs
@@ -0,0 +1,11 @@
+defmodule Explorer.Repo.Migrations.CreateBtreeGinExtension do
+ use Ecto.Migration
+
+ def up do
+ execute("CREATE EXTENSION IF NOT EXISTS btree_gin")
+ end
+
+ def down do
+ execute("DROP EXTENSION IF EXISTS btree_gin")
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20231213090140_add_token_transfers_token_contract_address_token_ids_index.exs b/apps/explorer/priv/repo/migrations/20231213090140_add_token_transfers_token_contract_address_token_ids_index.exs
new file mode 100644
index 000000000000..7636fc7b3c2f
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20231213090140_add_token_transfers_token_contract_address_token_ids_index.exs
@@ -0,0 +1,26 @@
+defmodule Explorer.Repo.Migrations.AddTokenTransfersTokenContractAddressTokenIdsIndex do
+ use Ecto.Migration
+ @disable_ddl_transaction true
+ @disable_migration_lock true
+
+ def up do
+ create(
+ index(
+ :token_transfers,
+ [:token_contract_address_hash, :token_ids],
+ name: "token_transfers_token_contract_address_hash_token_ids_index",
+ using: "GIN",
+ concurrently: true
+ )
+ )
+ end
+
+ def down do
+ drop_if_exists(
+ index(:token_transfers, [:token_contract_address_hash, :token_ids],
+ name: :token_transfers_token_contract_address_hash_token_ids_index
+ ),
+ concurrently: true
+ )
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20231213101235_drop_token_transfers_token_ids_index.exs b/apps/explorer/priv/repo/migrations/20231213101235_drop_token_transfers_token_ids_index.exs
new file mode 100644
index 000000000000..0065d2e26312
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20231213101235_drop_token_transfers_token_ids_index.exs
@@ -0,0 +1,7 @@
+defmodule Explorer.Repo.Migrations.DropTokenTransfersTokenIdsIndex do
+ use Ecto.Migration
+
+ def change do
+ drop_if_exists(index(:token_transfers, [:token_ids], name: :token_transfers_token_ids_index))
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20231213152332_alter_log_topic_columns_type.exs b/apps/explorer/priv/repo/migrations/20231213152332_alter_log_topic_columns_type.exs
new file mode 100644
index 000000000000..649c95d0dbf1
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20231213152332_alter_log_topic_columns_type.exs
@@ -0,0 +1,18 @@
+defmodule Explorer.Repo.Migrations.AlterLogTopicColumnsType do
+ use Ecto.Migration
+
+ def change do
+ execute("""
+ ALTER TABLE logs
+ ALTER COLUMN first_topic TYPE bytea
+ USING CAST(REPLACE(first_topic, '0x', '\\x') as bytea),
+ ALTER COLUMN second_topic TYPE bytea
+ USING CAST(REPLACE(second_topic, '0x', '\\x') as bytea),
+ ALTER COLUMN third_topic TYPE bytea
+ USING CAST(REPLACE(third_topic, '0x', '\\x') as bytea),
+ ALTER COLUMN fourth_topic TYPE bytea
+ USING CAST(REPLACE(fourth_topic, '0x', '\\x') as bytea)
+ ;
+ """)
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20231215094615_drop_token_transfers_token_id_column.exs b/apps/explorer/priv/repo/migrations/20231215094615_drop_token_transfers_token_id_column.exs
new file mode 100644
index 000000000000..df8637071b31
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20231215094615_drop_token_transfers_token_id_column.exs
@@ -0,0 +1,12 @@
+defmodule Explorer.Repo.Migrations.DropTokenTransfersTokenIdColumn do
+ use Ecto.Migration
+
+ def change do
+ drop(index(:token_transfers, [:token_id]))
+ drop(index(:token_transfers, [:token_contract_address_hash, "token_id DESC", "block_number DESC"]))
+
+ alter table(:token_transfers) do
+ remove(:token_id)
+ end
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20231215104320_drop_unused_actb_indexes.exs b/apps/explorer/priv/repo/migrations/20231215104320_drop_unused_actb_indexes.exs
new file mode 100644
index 000000000000..0f8ad39f3bc0
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20231215104320_drop_unused_actb_indexes.exs
@@ -0,0 +1,21 @@
+defmodule Explorer.Repo.Migrations.DropUnusedActbIndexes do
+ use Ecto.Migration
+
+ def change do
+ drop(
+ index(
+ :address_current_token_balances,
+ [:value],
+ name: :address_current_token_balances_value,
+ where: "value IS NOT NULL"
+ )
+ )
+
+ drop(
+ index(
+ :address_current_token_balances,
+ [:address_hash, :block_number, :token_contract_address_hash]
+ )
+ )
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20231215115638_drop_unused_logs_type_index.exs b/apps/explorer/priv/repo/migrations/20231215115638_drop_unused_logs_type_index.exs
new file mode 100644
index 000000000000..fc7df4ddce8c
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20231215115638_drop_unused_logs_type_index.exs
@@ -0,0 +1,11 @@
+defmodule Explorer.Repo.Migrations.DropUnusedLogsTypeIndex do
+ use Ecto.Migration
+
+ def change do
+ drop(index(:logs, [:type], name: :logs_type_index))
+
+ alter table(:logs) do
+ remove(:type)
+ end
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20231215132609_add_index_blocks_refetch_needed.exs b/apps/explorer/priv/repo/migrations/20231215132609_add_index_blocks_refetch_needed.exs
new file mode 100644
index 000000000000..aef72e5ed81c
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20231215132609_add_index_blocks_refetch_needed.exs
@@ -0,0 +1,15 @@
+defmodule Explorer.Repo.Migrations.AddIndexBlocksRefetchNeeded do
+ use Ecto.Migration
+
+ def up do
+ execute("""
+ CREATE INDEX consensus_block_hashes_refetch_needed ON blocks(hash) WHERE consensus and refetch_needed;
+ """)
+ end
+
+ def down do
+ execute("""
+ DROP INDEX consensus_block_hashes_refetch_needed;
+ """)
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20231225113850_transactions_asc_indices.exs b/apps/explorer/priv/repo/migrations/20231225113850_transactions_asc_indices.exs
new file mode 100644
index 000000000000..b0f668e89cd5
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20231225113850_transactions_asc_indices.exs
@@ -0,0 +1,47 @@
+defmodule Explorer.Repo.Migrations.TransactionsAscIndices do
+ use Ecto.Migration
+
+ def change do
+ create(
+ index(
+ :transactions,
+ [
+ :from_address_hash,
+ "block_number ASC NULLS LAST",
+ "index ASC NULLS LAST",
+ "inserted_at ASC",
+ "hash DESC"
+ ],
+ name: "transactions_from_address_hash_with_pending_index_asc"
+ )
+ )
+
+ create(
+ index(
+ :transactions,
+ [
+ :to_address_hash,
+ "block_number ASC NULLS LAST",
+ "index ASC NULLS LAST",
+ "inserted_at ASC",
+ "hash DESC"
+ ],
+ name: "transactions_to_address_hash_with_pending_index_asc"
+ )
+ )
+
+ create(
+ index(
+ :transactions,
+ [
+ :created_contract_address_hash,
+ "block_number ASC NULLS LAST",
+ "index ASC NULLS LAST",
+ "inserted_at ASC",
+ "hash DESC"
+ ],
+ name: "transactions_created_contract_address_hash_with_pending_index_a"
+ )
+ )
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20231225115026_logs_asc_index.exs b/apps/explorer/priv/repo/migrations/20231225115026_logs_asc_index.exs
new file mode 100644
index 000000000000..7e7fc0963d6a
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20231225115026_logs_asc_index.exs
@@ -0,0 +1,7 @@
+defmodule Explorer.Repo.Migrations.LogsAscIndex do
+ use Ecto.Migration
+
+ def change do
+ create(index(:logs, ["block_number ASC, index ASC"]))
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20231225115100_token_transfers_asc_index.exs b/apps/explorer/priv/repo/migrations/20231225115100_token_transfers_asc_index.exs
new file mode 100644
index 000000000000..084ce83adb35
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20231225115100_token_transfers_asc_index.exs
@@ -0,0 +1,7 @@
+defmodule Explorer.Repo.Migrations.TokenTransfersAscIndex do
+ use Ecto.Migration
+
+ def change do
+ create(index(:token_transfers, ["block_number ASC", "log_index ASC"]))
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20240103094720_constrain_null_date_market_history.exs b/apps/explorer/priv/repo/migrations/20240103094720_constrain_null_date_market_history.exs
new file mode 100644
index 000000000000..ab43538e4e1e
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20240103094720_constrain_null_date_market_history.exs
@@ -0,0 +1,9 @@
+defmodule Explorer.Repo.Migrations.ConstrainNullDateMarketHistory do
+ use Ecto.Migration
+
+ def change do
+ alter table(:market_history) do
+ modify(:date, :date, null: false, from: {:date, null: true})
+ end
+ end
+end
diff --git a/apps/explorer/priv/rsk/migrations/20230724094744_add_rootstock_fields_to_blocks.exs b/apps/explorer/priv/rsk/migrations/20230724094744_add_rootstock_fields_to_blocks.exs
new file mode 100644
index 000000000000..40bbbc79793b
--- /dev/null
+++ b/apps/explorer/priv/rsk/migrations/20230724094744_add_rootstock_fields_to_blocks.exs
@@ -0,0 +1,13 @@
+defmodule Explorer.Repo.RSK.Migrations.AddRootstockFieldsToBlocks do
+ use Ecto.Migration
+
+ def change do
+ alter table(:blocks) do
+ add(:minimum_gas_price, :decimal)
+ add(:bitcoin_merged_mining_header, :bytea)
+ add(:bitcoin_merged_mining_coinbase_transaction, :bytea)
+ add(:bitcoin_merged_mining_merkle_proof, :bytea)
+ add(:hash_for_merged_mining, :bytea)
+ end
+ end
+end
diff --git a/apps/explorer/priv/rsk/migrations/20231004101922_populate_pending_block_ops_with_historic_blocks.exs b/apps/explorer/priv/rsk/migrations/20231004101922_populate_pending_block_ops_with_historic_blocks.exs
new file mode 100644
index 000000000000..cef3c3968be8
--- /dev/null
+++ b/apps/explorer/priv/rsk/migrations/20231004101922_populate_pending_block_ops_with_historic_blocks.exs
@@ -0,0 +1,16 @@
+defmodule Explorer.Repo.RSK.Migrations.PopulatePendingBlockOpsWithHistoricBlocks do
+ use Ecto.Migration
+
+ def change do
+ execute("""
+ INSERT INTO pending_block_operations
+ SELECT b.hash, NOW(), NOW(), b.number
+ FROM blocks b
+ LEFT JOIN pending_block_operations pbo
+ ON b.hash = pbo.block_hash
+ WHERE consensus IS TRUE
+ and b.hash IS NOT NULL
+ and pbo.block_hash IS NULL;
+ """)
+ end
+end
diff --git a/apps/explorer/priv/suave/migrations/20230921120210_add_suave_transaction_fields.exs b/apps/explorer/priv/suave/migrations/20230921120210_add_suave_transaction_fields.exs
new file mode 100644
index 000000000000..2476d0b42fab
--- /dev/null
+++ b/apps/explorer/priv/suave/migrations/20230921120210_add_suave_transaction_fields.exs
@@ -0,0 +1,24 @@
+defmodule Explorer.Repo.Suave.Migrations.AddSuaveTransactionFields do
+ use Ecto.Migration
+
+ def change do
+ alter table(:transactions) do
+ add(:execution_node_hash, :bytea, null: true)
+ add(:wrapped_type, :integer, null: true)
+ add(:wrapped_nonce, :integer, null: true)
+ add(:wrapped_to_address_hash, :bytea, null: true)
+ add(:wrapped_gas, :numeric, precision: 100, null: true)
+ add(:wrapped_gas_price, :numeric, precision: 100, null: true)
+ add(:wrapped_max_priority_fee_per_gas, :numeric, precision: 100, null: true)
+ add(:wrapped_max_fee_per_gas, :numeric, precision: 100, null: true)
+ add(:wrapped_value, :numeric, precision: 100, null: true)
+ add(:wrapped_input, :bytea, null: true)
+ add(:wrapped_v, :numeric, precision: 100, null: true)
+ add(:wrapped_r, :numeric, precision: 100, null: true)
+ add(:wrapped_s, :numeric, precision: 100, null: true)
+ add(:wrapped_hash, :bytea, null: true)
+ end
+
+ create(index(:transactions, :execution_node_hash))
+ end
+end
diff --git a/apps/explorer/test/explorer/account/notify/email_test.exs b/apps/explorer/test/explorer/account/notifier/email_test.exs
similarity index 80%
rename from apps/explorer/test/explorer/account/notify/email_test.exs
rename to apps/explorer/test/explorer/account/notifier/email_test.exs
index 34e5a57ad6d9..184e7d33b8a2 100644
--- a/apps/explorer/test/explorer/account/notify/email_test.exs
+++ b/apps/explorer/test/explorer/account/notifier/email_test.exs
@@ -1,9 +1,6 @@
-defmodule Explorer.Account.Notify.EmailTest do
+defmodule Explorer.Account.Notifier.EmailTest do
use ExUnit.Case
- alias Explorer.Chain.Address
- alias Explorer.Chain.Transaction
-
alias Explorer.Account.{
Identity,
Watchlist,
@@ -23,8 +20,11 @@ defmodule Explorer.Account.Notify.EmailTest do
setup do
host = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:host]
path = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:path]
+ scheme = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:scheme]
- Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, url: [host: "localhost", path: "/"])
+ Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint,
+ url: [scheme: "https", host: "eth.blockscout.com", path: "/", port: 443]
+ )
Application.put_env(:explorer, Explorer.Account,
sendgrid: [
@@ -36,7 +36,7 @@ defmodule Explorer.Account.Notify.EmailTest do
:ok
on_exit(fn ->
- Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, url: [host: host, path: path])
+ Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, url: [scheme: scheme, host: host, path: path])
end)
end
@@ -47,7 +47,6 @@ defmodule Explorer.Account.Notify.EmailTest do
{:ok, from_hash} = string_to_address_hash("0x092D537737E767Dae48c28aE509f34094496f030")
{:ok, to_hash} = string_to_address_hash("0xE1F4dd38f00B0D8D4d2b4B5010bE53F2A0b934E5")
- to_address = %Address{hash: to_hash}
identity = %Identity{
uid: "foo|bar",
@@ -95,20 +94,20 @@ defmodule Explorer.Account.Notify.EmailTest do
dynamic_template_data: %{
"address_hash" => "0xe1f4dd38f00b0d8d4d2b4b5010be53f2a0b934e5",
"address_name" => "wallet",
- "address_url" => "https://localhost//address/0xe1f4dd38f00b0d8d4d2b4b5010be53f2a0b934e5",
+ "address_url" => "https://eth.blockscout.com/address/0xe1f4dd38f00b0d8d4d2b4b5010be53f2a0b934e5",
"amount" => Decimal.new(1),
"block_number" => 24_121_177,
- "block_url" => "https://localhost/block/24121177",
+ "block_url" => "https://eth.blockscout.com/block/24121177",
"direction" => "received at",
"from_address_hash" => "0x092d537737e767dae48c28ae509f34094496f030",
- "from_url" => "https://localhost//address/0x092d537737e767dae48c28ae509f34094496f030",
+ "from_url" => "https://eth.blockscout.com/address/0x092d537737e767dae48c28ae509f34094496f030",
"method" => "transfer",
"name" => "wallet",
"to_address_hash" => "0xe1f4dd38f00b0d8d4d2b4b5010be53f2a0b934e5",
- "to_url" => "https://localhost//address/0xe1f4dd38f00b0d8d4d2b4b5010be53f2a0b934e5",
+ "to_url" => "https://eth.blockscout.com/address/0xe1f4dd38f00b0d8d4d2b4b5010be53f2a0b934e5",
"transaction_hash" => "0x5d5ff210261f1b2d6e4af22ea494f428f9997d4ab614a629d4f1390004b3e80d",
"transaction_url" =>
- "https://localhost//tx/0x5d5ff210261f1b2d6e4af22ea494f428f9997d4ab614a629d4f1390004b3e80d",
+ "https://eth.blockscout.com/tx/0x5d5ff210261f1b2d6e4af22ea494f428f9997d4ab614a629d4f1390004b3e80d",
"tx_fee" => Decimal.new(210_000),
"username" => "John Snow"
},
diff --git a/apps/explorer/test/explorer/account/notify/notify_test.exs b/apps/explorer/test/explorer/account/notifier/notify_test.exs
similarity index 53%
rename from apps/explorer/test/explorer/account/notify/notify_test.exs
rename to apps/explorer/test/explorer/account/notifier/notify_test.exs
index 7433374aec0b..860b569f2a15 100644
--- a/apps/explorer/test/explorer/account/notify/notify_test.exs
+++ b/apps/explorer/test/explorer/account/notifier/notify_test.exs
@@ -1,4 +1,4 @@
-defmodule Explorer.Account.Notify.NotifyTest do
+defmodule Explorer.Account.Notifier.NotifyTest do
# use ExUnit.Case
use Explorer.DataCase
@@ -55,7 +55,7 @@ defmodule Explorer.Account.Notify.NotifyTest do
test "when address appears in watchlist" do
wa =
%WatchlistAddress{address_hash: address_hash} =
- build(:account_watchlist_address)
+ build(:account_watchlist_address, watch_coin_input: true)
|> Repo.account_repo().insert!()
_watchlist_address = Repo.preload(wa, watchlist: :identity)
@@ -86,5 +86,61 @@ defmodule Explorer.Account.Notify.NotifyTest do
assert wn.tx_fee == fee
assert wn.type == "COIN"
end
+
+ test "ignore new notification when limit is reached" do
+ old_envs = Application.get_env(:explorer, Explorer.Account)
+
+ Application.put_env(:explorer, Explorer.Account, Keyword.put(old_envs, :notifications_limit_for_30_days, 1))
+
+ wa =
+ %WatchlistAddress{address_hash: address_hash} =
+ build(:account_watchlist_address, watch_coin_input: true)
+ |> Repo.account_repo().insert!()
+
+ _watchlist_address = Repo.preload(wa, watchlist: :identity)
+
+ tx =
+ %Transaction{
+ from_address: _from_address,
+ to_address: _to_address,
+ block_number: _block_number,
+ hash: _tx_hash
+ } = with_block(insert(:transaction, to_address: %Chain.Address{hash: address_hash}))
+
+ {_, fee} = Chain.fee(tx, :gwei)
+ amount = Wei.to(tx.value, :ether)
+ notify = Notify.call([tx])
+
+ wn =
+ WatchlistNotification
+ |> first
+ |> Repo.account_repo().one()
+
+ assert notify == [[:ok]]
+
+ assert wn.amount == amount
+ assert wn.direction == "incoming"
+ assert wn.method == "transfer"
+ assert wn.subject == "Coin transaction"
+ assert wn.tx_fee == fee
+ assert wn.type == "COIN"
+ address = Repo.get(Chain.Address, address_hash)
+
+ tx =
+ %Transaction{
+ from_address: _from_address,
+ to_address: _to_address,
+ block_number: _block_number,
+ hash: _tx_hash
+ } = with_block(insert(:transaction, to_address: address))
+
+ Notify.call([tx])
+
+ WatchlistNotification
+ |> first
+ |> Repo.account_repo().one!()
+
+ Application.put_env(:explorer, Explorer.Account, old_envs)
+ end
end
end
diff --git a/apps/explorer/test/explorer/account/notify/summary_test.exs b/apps/explorer/test/explorer/account/notifier/summary_test.exs
similarity index 99%
rename from apps/explorer/test/explorer/account/notify/summary_test.exs
rename to apps/explorer/test/explorer/account/notifier/summary_test.exs
index 9fcfb9230d00..2f899d8e6ff8 100644
--- a/apps/explorer/test/explorer/account/notify/summary_test.exs
+++ b/apps/explorer/test/explorer/account/notifier/summary_test.exs
@@ -1,4 +1,4 @@
-defmodule Explorer.Account.Notify.SummaryTest do
+defmodule Explorer.Account.Notifier.SummaryTest do
use Explorer.DataCase
import Explorer.Factory
diff --git a/apps/explorer/test/explorer/chain/address/current_token_balance_test.exs b/apps/explorer/test/explorer/chain/address/current_token_balance_test.exs
index e7a366377ee4..85d49a8c1955 100644
--- a/apps/explorer/test/explorer/chain/address/current_token_balance_test.exs
+++ b/apps/explorer/test/explorer/chain/address/current_token_balance_test.exs
@@ -157,7 +157,7 @@ defmodule Explorer.Chain.Address.CurrentTokenBalanceTest do
address.hash
|> CurrentTokenBalance.last_token_balances()
|> Repo.all()
- |> Enum.map(fn {token_balance, _} -> token_balance.address_hash end)
+ |> Enum.map(fn token_balance -> token_balance.address_hash end)
assert token_balances == [current_token_balance.address_hash]
end
@@ -195,7 +195,7 @@ defmodule Explorer.Chain.Address.CurrentTokenBalanceTest do
address.hash
|> CurrentTokenBalance.last_token_balances()
|> Repo.all()
- |> Enum.map(fn {token_balance, _} -> token_balance.address_hash end)
+ |> Enum.map(fn token_balance -> token_balance.address_hash end)
assert token_balances == [current_token_balance_a.address_hash]
end
diff --git a/apps/explorer/test/explorer/chain/address/token_balance_test.exs b/apps/explorer/test/explorer/chain/address/token_balance_test.exs
index 717a783767a5..3e1a8018289a 100644
--- a/apps/explorer/test/explorer/chain/address/token_balance_test.exs
+++ b/apps/explorer/test/explorer/chain/address/token_balance_test.exs
@@ -3,7 +3,6 @@ defmodule Explorer.Chain.Address.TokenBalanceTest do
alias Explorer.Repo
alias Explorer.Chain.Address.TokenBalance
- alias Explorer.Chain
describe "unfetched_token_balances/0" do
test "returns only the token balances that have value_fetched_at nil" do
@@ -47,6 +46,7 @@ defmodule Explorer.Chain.Address.TokenBalanceTest do
:token_balance,
address: burn_address,
token_contract_address_hash: token.contract_address_hash,
+ token_type: "ERC-721",
value_fetched_at: nil
)
@@ -78,7 +78,7 @@ defmodule Explorer.Chain.Address.TokenBalanceTest do
end
end
- describe "fetch_token_balance/3" do
+ describe "fetch_token_balance/4" do
test "returns the token balance for the given address" do
token_balance = insert(:token_balance)
diff --git a/apps/explorer/test/explorer/chain/address_test.exs b/apps/explorer/test/explorer/chain/address_test.exs
index 213913c766a8..c6ca32494652 100644
--- a/apps/explorer/test/explorer/chain/address_test.exs
+++ b/apps/explorer/test/explorer/chain/address_test.exs
@@ -6,6 +6,8 @@ defmodule Explorer.Chain.AddressTest do
alias Explorer.Chain.Address
alias Explorer.Repo
+ setup :verify_on_exit!
+
describe "changeset/2" do
test "with valid attributes" do
params = params_for(:address)
@@ -61,4 +63,74 @@ defmodule Explorer.Chain.AddressTest do
assert str("0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb") == "0xD1220A0Cf47c7B9BE7a2e6ba89F429762E7B9adB"
end
end
+
+ describe "list_top_addresses/0" do
+ test "without addresses with balance > 0" do
+ insert(:address, fetched_coin_balance: 0)
+ assert [] = Address.list_top_addresses()
+ end
+
+ test "with top addresses in order" do
+ address_hashes =
+ 4..1
+ |> Enum.map(&insert(:address, fetched_coin_balance: &1))
+ |> Enum.map(& &1.hash)
+
+ assert address_hashes ==
+ Address.list_top_addresses()
+ |> Enum.map(fn {address, _transaction_count} -> address end)
+ |> Enum.map(& &1.hash)
+ end
+
+ # flaky test
+ # test "with top addresses in order with matching value" do
+ # test_hashes =
+ # 4..0
+ # |> Enum.map(&Explorer.Chain.Hash.cast(Explorer.Chain.Hash.Address, &1))
+ # |> Enum.map(&elem(&1, 1))
+
+ # tail =
+ # 4..1
+ # |> Enum.map(&insert(:address, fetched_coin_balance: &1, hash: Enum.fetch!(test_hashes, &1 - 1)))
+ # |> Enum.map(& &1.hash)
+
+ # first_result_hash =
+ # :address
+ # |> insert(fetched_coin_balance: 4, hash: Enum.fetch!(test_hashes, 4))
+ # |> Map.fetch!(:hash)
+
+ # assert [first_result_hash | tail] ==
+ # Address.list_top_addresses()
+ # |> Enum.map(fn {address, _transaction_count} -> address end)
+ # |> Enum.map(& &1.hash)
+ # end
+
+ # flaky test
+ # test "paginates addresses" do
+ # test_hashes =
+ # 4..0
+ # |> Enum.map(&Explorer.Chain.Hash.cast(Explorer.Chain.Hash.Address, &1))
+ # |> Enum.map(&elem(&1, 1))
+
+ # result =
+ # 4..1
+ # |> Enum.map(&insert(:address, fetched_coin_balance: &1, hash: Enum.fetch!(test_hashes, &1 - 1)))
+ # |> Enum.map(& &1.hash)
+
+ # options = [paging_options: %PagingOptions{page_size: 1}]
+
+ # [{top_address, _}] = Chain.list_top_addresses(options)
+ # assert top_address.hash == List.first(result)
+
+ # tail_options = [
+ # paging_options: %PagingOptions{key: {top_address.fetched_coin_balance.value, top_address.hash}, page_size: 3}
+ # ]
+
+ # tail_result = tail_options |> Address.list_top_addresses() |> Enum.map(fn {address, _} -> address.hash end)
+
+ # [_ | expected_tail] = result
+
+ # assert tail_result == expected_tail
+ # end
+ end
end
diff --git a/apps/explorer/test/explorer/chain/block_test.exs b/apps/explorer/test/explorer/chain/block_test.exs
index 35f14c54795c..703cd8609a7b 100644
--- a/apps/explorer/test/explorer/chain/block_test.exs
+++ b/apps/explorer/test/explorer/chain/block_test.exs
@@ -2,7 +2,7 @@ defmodule Explorer.Chain.BlockTest do
use Explorer.DataCase
alias Ecto.Changeset
- alias Explorer.Chain.Block
+ alias Explorer.Chain.{Block, Wei}
describe "changeset/2" do
test "with valid attributes" do
@@ -58,4 +58,46 @@ defmodule Explorer.Chain.BlockTest do
assert Enum.member?(results, unrewarded_block.hash)
end
end
+
+ describe "block_reward_by_parts/1" do
+ setup do
+ {:ok, emission_reward: insert(:emission_reward)}
+ end
+
+ test "without uncles", %{emission_reward: %{reward: reward, block_range: range}} do
+ block = build(:block, number: range.from, base_fee_per_gas: 5, uncles: [])
+
+ tx1 = build(:transaction, gas_price: 1, gas_used: 1, block_number: block.number, block_hash: block.hash)
+ tx2 = build(:transaction, gas_price: 1, gas_used: 2, block_number: block.number, block_hash: block.hash)
+
+ tx3 =
+ build(:transaction,
+ gas_price: 1,
+ gas_used: 3,
+ block_number: block.number,
+ block_hash: block.hash,
+ max_priority_fee_per_gas: 1
+ )
+
+ expected_transaction_fees = %Wei{value: Decimal.new(6)}
+ expected_burnt_fees = %Wei{value: Decimal.new(30)}
+ expected_uncle_reward = %Wei{value: Decimal.new(0)}
+
+ assert %{
+ static_reward: ^reward,
+ transaction_fees: ^expected_transaction_fees,
+ burnt_fees: ^expected_burnt_fees,
+ uncle_reward: ^expected_uncle_reward
+ } = Block.block_reward_by_parts(block, [tx1, tx2, tx3])
+ end
+
+ test "with uncles", %{emission_reward: %{reward: reward, block_range: range}} do
+ block =
+ build(:block, number: range.from, uncles: ["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273311"])
+
+ expected_uncle_reward = Wei.mult(reward, Decimal.from_float(1 / 32))
+
+ assert %{uncle_reward: ^expected_uncle_reward} = Block.block_reward_by_parts(block, [])
+ end
+ end
end
diff --git a/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs b/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs
index 148b8e8872e3..a30d4070f66f 100644
--- a/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs
+++ b/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs
@@ -1,33 +1,80 @@
defmodule Explorer.Chain.Cache.GasPriceOracleTest do
use Explorer.DataCase
+ import Mox
+
alias Explorer.Chain.Cache.GasPriceOracle
- alias Explorer.Repo
+ alias Explorer.Counters.AverageBlockTime
+
+ @block %{
+ "difficulty" => "0x0",
+ "gasLimit" => "0x0",
+ "gasUsed" => "0x0",
+ "hash" => "0x29c850324e357f3c0c836d79860c5af55f7b651e5d7ee253c1af1b14908af49c",
+ "extraData" => "0x0",
+ "logsBloom" => "0x0",
+ "miner" => "0x0",
+ "number" => "0x1",
+ "parentHash" => "0x0",
+ "receiptsRoot" => "0x0",
+ "size" => "0x0",
+ "sha3Uncles" => "0x0",
+ "stateRoot" => "0x0",
+ "timestamp" => "0x0",
+ "baseFeePerGas" => "0x1DCD6500",
+ "totalDifficulty" => "0x0",
+ "transactions" => [
+ %{
+ "blockHash" => "0x29c850324e357f3c0c836d79860c5af55f7b651e5d7ee253c1af1b14908af49c",
+ "blockNumber" => "0x1",
+ "from" => "0x0",
+ "gas" => "0x0",
+ "gasPrice" => "0x0",
+ "hash" => "0xa2e81bb56b55ba3dab2daf76501b50dfaad240cccb905dbf89d65c7a84a4a48e",
+ "input" => "0x",
+ "nonce" => "0x0",
+ "r" => "0x0",
+ "s" => "0x0",
+ "to" => "0x0",
+ "transactionIndex" => "0x0",
+ "v" => "0x0",
+ "value" => "0x0"
+ }
+ ],
+ "transactionsRoot" => "0x0",
+ "uncles" => []
+ }
describe "get_average_gas_price/4" do
test "returns nil percentile values if no blocks in the DB" do
- assert {:ok,
- %{
- "slow" => nil,
- "average" => nil,
- "fast" => nil
- }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
+
+ assert {{:ok,
+ %{
+ slow: nil,
+ average: nil,
+ fast: nil
+ }}, []} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
end
test "returns nil percentile values if blocks are empty in the DB" do
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
+
insert(:block)
insert(:block)
insert(:block)
- assert {:ok,
- %{
- "slow" => nil,
- "average" => nil,
- "fast" => nil
- }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
+ assert {{:ok,
+ %{
+ slow: nil,
+ average: nil,
+ fast: nil
+ }}, []} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
end
test "returns nil percentile values for blocks with failed txs in the DB" do
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
+
block = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
:transaction
@@ -43,15 +90,17 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269"
)
- assert {:ok,
- %{
- "slow" => nil,
- "average" => nil,
- "fast" => nil
- }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
+ assert {{:ok,
+ %{
+ slow: nil,
+ average: nil,
+ fast: nil
+ }}, []} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
end
test "returns nil percentile values for transactions with 0 gas price aka 'whitelisted transactions' in the DB" do
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
+
block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729")
@@ -79,15 +128,17 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03"
)
- assert {:ok,
- %{
- "slow" => nil,
- "average" => nil,
- "fast" => nil
- }} = GasPriceOracle.get_average_gas_price(2, 35, 60, 90)
+ assert {{:ok,
+ %{
+ slow: nil,
+ average: nil,
+ fast: nil
+ }}, []} = GasPriceOracle.get_average_gas_price(2, 35, 60, 90)
end
test "returns the same percentile values if gas price is the same over transactions" do
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
+
block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729")
@@ -115,15 +166,17 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03"
)
- assert {:ok,
- %{
- "slow" => 1.0,
- "average" => 1.0,
- "fast" => 1.0
- }} = GasPriceOracle.get_average_gas_price(2, 35, 60, 90)
+ assert {{:ok,
+ %{
+ slow: %{price: 1.0},
+ average: %{price: 1.0},
+ fast: %{price: 1.0}
+ }}, _} = GasPriceOracle.get_average_gas_price(2, 35, 60, 90)
end
test "returns correct min gas price from the block" do
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
+
block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729")
@@ -163,15 +216,17 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
hash: "0x906b80861b4a0921acfbb91a7b527227b0d32adabc88bc73e8c52ff714e55016"
)
- assert {:ok,
- %{
- "slow" => 1.0,
- "average" => 1.0,
- "fast" => 1.0
- }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
+ assert {{:ok,
+ %{
+ slow: %{price: 1.0},
+ average: %{price: 2.0},
+ fast: %{price: 2.0}
+ }}, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
end
test "returns correct average percentile" do
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
+
block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729")
block3 = insert(:block, number: 102, hash: "0x659b2a1cc4dd1a5729900cf0c81c471d1c7891b2517bf9466f7fba56ead2fca0")
@@ -212,10 +267,230 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
hash: "0x7d4bc5569053fc29f471901e967c9e60205ac7a122b0e9a789683652c34cc11a"
)
- assert {:ok,
- %{
- "average" => 4.0
- }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
+ assert {{:ok,
+ %{
+ average: %{price: 3.34}
+ }}, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
+ end
+
+ test "returns correct gas price for EIP-1559 transactions" do
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
+
+ block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
+ block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729")
+
+ :transaction
+ |> insert(
+ status: :ok,
+ block_hash: block1.hash,
+ block_number: block1.number,
+ cumulative_gas_used: 884_322,
+ gas_used: 106_025,
+ index: 0,
+ gas_price: 1_000_000_000,
+ max_priority_fee_per_gas: 1_000_000_000,
+ max_fee_per_gas: 1_000_000_000,
+ hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269"
+ )
+
+ :transaction
+ |> insert(
+ status: :ok,
+ block_hash: block2.hash,
+ block_number: block2.number,
+ cumulative_gas_used: 884_322,
+ gas_used: 106_025,
+ index: 0,
+ gas_price: 1_000_000_000,
+ max_priority_fee_per_gas: 1_000_000_000,
+ max_fee_per_gas: 1_000_000_000,
+ hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03"
+ )
+
+ :transaction
+ |> insert(
+ status: :ok,
+ block_hash: block2.hash,
+ block_number: block2.number,
+ cumulative_gas_used: 884_322,
+ gas_used: 106_025,
+ index: 1,
+ gas_price: 3_000_000_000,
+ max_priority_fee_per_gas: 3_000_000_000,
+ max_fee_per_gas: 3_000_000_000,
+ hash: "0x906b80861b4a0921acfbb91a7b527227b0d32adabc88bc73e8c52ff714e55016"
+ )
+
+ assert {{:ok,
+ %{
+ # including base fee
+ slow: %{price: 1.5},
+ average: %{price: 2.5}
+ }}, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
+ end
+
+ test "return gas prices with time if available" do
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
+
+ block1 =
+ insert(:block,
+ number: 100,
+ hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391",
+ timestamp: ~U[2023-12-12 12:12:30.000000Z]
+ )
+
+ block2 =
+ insert(:block,
+ number: 101,
+ hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729",
+ timestamp: ~U[2023-12-12 12:13:00.000000Z]
+ )
+
+ :transaction
+ |> insert(
+ status: :ok,
+ block_hash: block1.hash,
+ block_number: block1.number,
+ block_timestamp: block1.timestamp,
+ cumulative_gas_used: 884_322,
+ gas_used: 106_025,
+ index: 0,
+ gas_price: 1_000_000_000,
+ max_priority_fee_per_gas: 1_000_000_000,
+ max_fee_per_gas: 1_000_000_000,
+ hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269",
+ earliest_processing_start: ~U[2023-12-12 12:12:00.000000Z]
+ )
+
+ :transaction
+ |> insert(
+ status: :ok,
+ block_hash: block2.hash,
+ block_number: block2.number,
+ block_timestamp: block2.timestamp,
+ cumulative_gas_used: 884_322,
+ gas_used: 106_025,
+ index: 0,
+ gas_price: 1_000_000_000,
+ max_priority_fee_per_gas: 1_000_000_000,
+ max_fee_per_gas: 1_000_000_000,
+ hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03",
+ earliest_processing_start: ~U[2023-12-12 12:12:00.000000Z]
+ )
+
+ :transaction
+ |> insert(
+ status: :ok,
+ block_hash: block2.hash,
+ block_number: block2.number,
+ block_timestamp: block2.timestamp,
+ cumulative_gas_used: 884_322,
+ gas_used: 106_025,
+ index: 1,
+ gas_price: 3_000_000_000,
+ max_priority_fee_per_gas: 3_000_000_000,
+ max_fee_per_gas: 3_000_000_000,
+ hash: "0x906b80861b4a0921acfbb91a7b527227b0d32adabc88bc73e8c52ff714e55016",
+ earliest_processing_start: ~U[2023-12-12 12:12:55.000000Z]
+ )
+
+ assert {{
+ :ok,
+ %{
+ average: %{price: 2.5, time: 15000.0},
+ fast: %{price: 2.5, time: 15000.0},
+ slow: %{price: 1.5, time: 17500.0}
+ }
+ }, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
+ end
+
+ test "return gas prices with average block time if earliest_processing_start is not available" do
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
+ old_env = Application.get_env(:explorer, AverageBlockTime)
+ Application.put_env(:explorer, AverageBlockTime, enabled: true, cache_period: 1_800_000)
+ start_supervised!(AverageBlockTime)
+
+ block_number = 99_999_999
+ first_timestamp = ~U[2023-12-12 12:12:30.000000Z]
+
+ Enum.each(1..100, fn i ->
+ insert(:block,
+ number: block_number + 1 + i,
+ consensus: true,
+ timestamp: Timex.shift(first_timestamp, seconds: -(101 - i) - 12)
+ )
+ end)
+
+ block1 =
+ insert(:block,
+ number: block_number + 102,
+ consensus: true,
+ timestamp: Timex.shift(first_timestamp, seconds: -10)
+ )
+
+ block2 =
+ insert(:block,
+ number: block_number + 103,
+ consensus: true,
+ timestamp: Timex.shift(first_timestamp, seconds: -7)
+ )
+
+ AverageBlockTime.refresh()
+
+ :transaction
+ |> insert(
+ status: :ok,
+ block_hash: block1.hash,
+ block_number: block1.number,
+ cumulative_gas_used: 884_322,
+ gas_used: 106_025,
+ index: 0,
+ gas_price: 1_000_000_000,
+ max_priority_fee_per_gas: 1_000_000_000,
+ max_fee_per_gas: 1_000_000_000,
+ hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269"
+ )
+
+ :transaction
+ |> insert(
+ status: :ok,
+ block_hash: block2.hash,
+ block_number: block2.number,
+ cumulative_gas_used: 884_322,
+ gas_used: 106_025,
+ index: 0,
+ gas_price: 1_000_000_000,
+ max_priority_fee_per_gas: 1_000_000_000,
+ max_fee_per_gas: 1_000_000_000,
+ hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03"
+ )
+
+ :transaction
+ |> insert(
+ status: :ok,
+ block_hash: block2.hash,
+ block_number: block2.number,
+ cumulative_gas_used: 884_322,
+ gas_used: 106_025,
+ index: 1,
+ gas_price: 3_000_000_000,
+ max_priority_fee_per_gas: 3_000_000_000,
+ max_fee_per_gas: 3_000_000_000,
+ hash: "0x906b80861b4a0921acfbb91a7b527227b0d32adabc88bc73e8c52ff714e55016"
+ )
+
+ AverageBlockTime.refresh()
+
+ assert {{
+ :ok,
+ %{
+ average: %{price: 2.5, time: 1000.0},
+ fast: %{price: 2.5, time: 1000.0},
+ slow: %{price: 1.5, time: 1000.0}
+ }
+ }, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
+
+ Application.put_env(:explorer, AverageBlockTime, old_env)
end
end
end
diff --git a/apps/explorer/test/explorer/chain/cache/rootstock_locked_btc_test.exs b/apps/explorer/test/explorer/chain/cache/rootstock_locked_btc_test.exs
new file mode 100644
index 000000000000..46d0d033f30f
--- /dev/null
+++ b/apps/explorer/test/explorer/chain/cache/rootstock_locked_btc_test.exs
@@ -0,0 +1,38 @@
+defmodule Explorer.Chain.Cache.RootstockLockedBTCTest do
+ use Explorer.DataCase
+
+ alias Explorer.Chain.Cache.RootstockLockedBTC
+ alias Explorer.Chain.{Transaction, Wei}
+
+ @bridge_address "0x0000000000000000000000000000000001000006"
+
+ setup do
+ transaction_configuration = Application.get_env(:explorer, Transaction)
+ Application.put_env(:explorer, Transaction, rootstock_bridge_address: @bridge_address)
+
+ :ok
+
+ Supervisor.terminate_child(Explorer.Supervisor, RootstockLockedBTC.child_id())
+ Supervisor.restart_child(Explorer.Supervisor, RootstockLockedBTC.child_id())
+
+ on_exit(fn ->
+ Application.put_env(:explorer, Transaction, transaction_configuration)
+ end)
+
+ :ok
+ end
+
+ test "returns nil in case if there is no bridged address in the database" do
+ result = RootstockLockedBTC.get_locked_value()
+
+ assert is_nil(result)
+ end
+
+ test "updates cache if initial value is zero and returns converted wei" do
+ insert(:address, hash: @bridge_address, fetched_coin_balance: 42_000_000_000_000_000_000)
+
+ result = RootstockLockedBTC.get_locked_value()
+
+ assert result == Wei.from(Decimal.new(21_000_000), :ether) |> Wei.sub(Wei.from(Decimal.new(42), :ether))
+ end
+end
diff --git a/apps/explorer/test/explorer/chain/csv_export/address_internal_transaction_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/csv_export/address_internal_transaction_csv_exporter_test.exs
index be9a2c4cd6e3..a9687dd0aae5 100644
--- a/apps/explorer/test/explorer/chain/csv_export/address_internal_transaction_csv_exporter_test.exs
+++ b/apps/explorer/test/explorer/chain/csv_export/address_internal_transaction_csv_exporter_test.exs
@@ -2,7 +2,7 @@ defmodule Explorer.Chain.CSVExport.AddressInternalTransactionCsvExporterTest do
use Explorer.DataCase
alias Explorer.Chain.CSVExport.AddressInternalTransactionCsvExporter
- alias Explorer.Chain.Wei
+ alias Explorer.Chain.{Address, Wei}
describe "export/3" do
test "exports address internal transactions to csv" do
@@ -28,7 +28,7 @@ defmodule Explorer.Chain.CSVExport.AddressInternalTransactionCsvExporterTest do
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
res =
- address
+ address.hash
|> AddressInternalTransactionCsvExporter.export(from_period, to_period)
|> Enum.to_list()
|> Enum.drop(1)
@@ -102,8 +102,8 @@ defmodule Explorer.Chain.CSVExport.AddressInternalTransactionCsvExporterTest do
assert result.block_hash == to_string(internal_transaction.block_hash)
assert result.transaction_index == to_string(internal_transaction.transaction_index)
assert result.timestamp
- assert result.from_address_hash == to_string(internal_transaction.from_address_hash)
- assert result.to_address_hash == to_string(internal_transaction.to_address_hash)
+ assert result.from_address_hash == Address.checksum(internal_transaction.from_address_hash)
+ assert result.to_address_hash == Address.checksum(internal_transaction.to_address_hash)
assert result.created_contract_address_hash == to_string(internal_transaction.created_contract_address_hash)
assert result.type == to_string(internal_transaction.type)
assert result.call_type == to_string(internal_transaction.call_type)
@@ -179,7 +179,7 @@ defmodule Explorer.Chain.CSVExport.AddressInternalTransactionCsvExporterTest do
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
result =
- address
+ address.hash
|> AddressInternalTransactionCsvExporter.export(from_period, to_period)
|> Enum.to_list()
|> Enum.drop(1)
diff --git a/apps/explorer/test/explorer/chain/csv_export/address_log_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/csv_export/address_log_csv_exporter_test.exs
index e7fd8b7ec343..e70b2eb9cb25 100644
--- a/apps/explorer/test/explorer/chain/csv_export/address_log_csv_exporter_test.exs
+++ b/apps/explorer/test/explorer/chain/csv_export/address_log_csv_exporter_test.exs
@@ -1,8 +1,19 @@
defmodule Explorer.Chain.AddressLogCsvExporterTest do
use Explorer.DataCase
+ alias Explorer.Chain.Address
alias Explorer.Chain.CSVExport.AddressLogCsvExporter
+ @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65"
+ @second_topic_hex_string_1 "0x00000000000000000000000098a9dc37d3650b5b30d6c12789b3881ee0b70c16"
+ @third_topic_hex_string_1 "0x0000000000000000000000005079fc00f00f30000e0c8c083801cfde000008b6"
+ @fourth_topic_hex_string_1 "0x8c9b7729443a4444242342b2ca385a239a5c1d76a88473e1cd2ab0c70dd1b9c7"
+
+ defp topic(topic_hex_string) do
+ {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string)
+ topic
+ end
+
describe "export/3" do
test "exports address logs to csv" do
address = insert(:address)
@@ -20,17 +31,17 @@ defmodule Explorer.Chain.AddressLogCsvExporterTest do
block: transaction.block,
block_number: transaction.block_number,
data: "0x12",
- first_topic: "0x13",
- second_topic: "0x14",
- third_topic: "0x15",
- fourth_topic: "0x16"
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_1),
+ third_topic: topic(@third_topic_hex_string_1),
+ fourth_topic: topic(@fourth_topic_hex_string_1)
)
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
[result] =
- address
+ address.hash
|> AddressLogCsvExporter.export(from_period, to_period)
|> Enum.to_list()
|> Enum.drop(1)
@@ -74,7 +85,7 @@ defmodule Explorer.Chain.AddressLogCsvExporterTest do
assert result.index == to_string(log.index)
assert result.block_number == to_string(log.block_number)
assert result.block_hash == to_string(log.block_hash)
- assert result.address == String.downcase(to_string(log.address))
+ assert result.address == Address.checksum(log.address.hash)
assert result.data == to_string(log.data)
assert result.first_topic == to_string(log.first_topic)
assert result.second_topic == to_string(log.second_topic)
@@ -106,7 +117,7 @@ defmodule Explorer.Chain.AddressLogCsvExporterTest do
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
result =
- address
+ address.hash
|> AddressLogCsvExporter.export(from_period, to_period)
|> Enum.to_list()
|> Enum.drop(1)
diff --git a/apps/explorer/test/explorer/chain/csv_export/address_token_transfer_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/csv_export/address_token_transfer_csv_exporter_test.exs
index ca584c19fa75..88eeb2982530 100644
--- a/apps/explorer/test/explorer/chain/csv_export/address_token_transfer_csv_exporter_test.exs
+++ b/apps/explorer/test/explorer/chain/csv_export/address_token_transfer_csv_exporter_test.exs
@@ -1,6 +1,7 @@
defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do
use Explorer.DataCase
+ alias Explorer.Chain.Address
alias Explorer.Chain.CSVExport.AddressTokenTransferCsvExporter
describe "export/3" do
@@ -19,7 +20,7 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
[result] =
- address
+ address.hash
|> AddressTokenTransferCsvExporter.export(from_period, to_period)
|> Enum.to_list()
|> Enum.drop(1)
@@ -67,9 +68,9 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do
assert result.block_number == to_string(transaction.block_number)
assert result.tx_hash == to_string(transaction.hash)
- assert result.from_address == token_transfer.from_address_hash |> to_string() |> String.downcase()
- assert result.to_address == token_transfer.to_address_hash |> to_string() |> String.downcase()
- assert result.timestamp == to_string(transaction.block.timestamp)
+ assert result.from_address == Address.checksum(token_transfer.from_address_hash)
+ assert result.to_address == Address.checksum(token_transfer.to_address_hash)
+ assert result.timestamp == to_string(transaction.block_timestamp)
assert result.type == "OUT"
end
@@ -110,7 +111,7 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
result =
- address
+ address.hash
|> AddressTokenTransferCsvExporter.export(from_period, to_period)
|> Enum.to_list()
|> Enum.drop(1)
diff --git a/apps/explorer/test/explorer/chain/csv_export/address_transaction_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/csv_export/address_transaction_csv_exporter_test.exs
index 2035b3465c15..955cad7a0673 100644
--- a/apps/explorer/test/explorer/chain/csv_export/address_transaction_csv_exporter_test.exs
+++ b/apps/explorer/test/explorer/chain/csv_export/address_transaction_csv_exporter_test.exs
@@ -18,7 +18,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
[result] =
- address
+ address.hash
|> AddressTransactionCsvExporter.export(from_period, to_period)
|> Enum.to_list()
|> Enum.drop(1)
@@ -117,7 +117,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
result =
- address
+ address.hash
|> AddressTransactionCsvExporter.export(from_period, to_period)
|> Enum.to_list()
|> Enum.drop(1)
diff --git a/apps/explorer/test/explorer/chain/import/runner/address/current_token_balances_test.exs b/apps/explorer/test/explorer/chain/import/runner/address/current_token_balances_test.exs
index 31eba0553de0..fa8d97e0a5c2 100644
--- a/apps/explorer/test/explorer/chain/import/runner/address/current_token_balances_test.exs
+++ b/apps/explorer/test/explorer/chain/import/runner/address/current_token_balances_test.exs
@@ -218,7 +218,8 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalancesTest do
address_hash: address.hash,
block_number: 2,
token_contract_address_hash: token.contract_address_hash,
- value: Decimal.new(200)
+ value: Decimal.new(200),
+ value_fetched_at: DateTime.utc_now()
},
options
)
@@ -300,7 +301,8 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalancesTest do
address_hash: address_hash,
token_contract_address_hash: token_contract_address_hash,
block_number: block_number,
- value: value
+ value: value,
+ value_fetched_at: DateTime.utc_now()
},
options
)
@@ -344,7 +346,8 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalancesTest do
address_hash: address_hash,
token_contract_address_hash: token_contract_address_hash,
block_number: block_number,
- value: value
+ value: value,
+ value_fetched_at: DateTime.utc_now()
},
options
)
@@ -404,13 +407,15 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalancesTest do
address_hash: non_holder_becomes_holder_address_hash,
token_contract_address_hash: token_contract_address_hash,
block_number: block_number,
- value: non_holder_becomes_holder_value
+ value: non_holder_becomes_holder_value,
+ value_fetched_at: DateTime.utc_now()
},
%{
address_hash: holder_becomes_non_holder_address_hash,
token_contract_address_hash: token_contract_address_hash,
block_number: block_number,
- value: holder_becomes_non_holder_value
+ value: holder_becomes_non_holder_value,
+ value_fetched_at: DateTime.utc_now()
}
],
options
diff --git a/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs b/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs
index 15725869bd85..40034eb84148 100644
--- a/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs
+++ b/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs
@@ -208,7 +208,7 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalancesTest do
%{
address_token_balances: [
%TokenBalance{
- address_hash: address_hash,
+ address_hash: ^address_hash,
block_number: ^block_number,
token_contract_address_hash: ^token_contract_address_hash,
token_id: nil,
diff --git a/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs b/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs
index ba012aba47bd..7e79c20916f3 100644
--- a/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs
+++ b/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs
@@ -90,6 +90,8 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do
assert count(Address.CurrentTokenBalance) == 1
+ insert(:block, number: block_number, consensus: true)
+
assert {:ok,
%{
delete_address_current_token_balances: [
@@ -136,6 +138,8 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do
previous_block_number = block_number - 1
+ insert(:block, number: block_number, consensus: true)
+
assert {:ok,
%{
delete_address_current_token_balances: [
@@ -187,6 +191,8 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do
# Token must exist with non-`nil` `holder_count` for `blocks_update_token_holder_counts` to update
update_holder_count!(token_contract_address_hash, 0)
+ insert(:block, number: block_number, consensus: true)
+
block_params = params_for(:block, hash: block_hash, miner_hash: miner_hash, number: block_number, consensus: true)
%Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, block_params)
@@ -219,6 +225,8 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do
# Token must exist with non-`nil` `holder_count` for `blocks_update_token_holder_counts` to update
update_holder_count!(token_contract_address_hash, 1)
+ insert(:block, number: block_number, consensus: true)
+
block_params = params_for(:block, hash: block_hash, miner_hash: miner_hash, number: block_number, consensus: true)
%Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, block_params)
@@ -354,13 +362,172 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do
%Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, new_block)
%Ecto.Changeset{valid?: true, changes: block_changes1} = Block.changeset(%Block{}, new_block1)
- result =
- Multi.new()
- |> Blocks.run([block_changes, block_changes1], options)
- |> Repo.transaction()
+ Multi.new()
+ |> Blocks.run([block_changes, block_changes1], options)
+ |> Repo.transaction()
assert %{block_number: ^number, block_hash: ^hash} = Repo.one(PendingBlockOperation)
end
+
+ test "change instance owner if was token transfer in older blocks",
+ %{consensus_block: %{hash: block_hash, miner_hash: miner_hash, number: block_number}, options: options} do
+ block_number = block_number + 2
+ consensus_block = insert(:block, %{hash: block_hash, number: block_number})
+
+ transaction =
+ :transaction
+ |> insert()
+ |> with_block(consensus_block)
+
+ token_address = insert(:contract_address)
+ insert(:token, contract_address: token_address, type: "ERC-721")
+ id = Decimal.new(1)
+
+ tt =
+ insert(:token_transfer,
+ token_ids: [id],
+ transaction: transaction,
+ token_contract_address: token_address,
+ block_number: block_number,
+ block: consensus_block,
+ log_index: 123
+ )
+
+ %{hash: hash_1} = params_for(:block, consensus: true, miner_hash: miner_hash)
+ consensus_block_1 = insert(:block, %{hash: hash_1, number: block_number - 1})
+
+ transaction =
+ :transaction
+ |> insert()
+ |> with_block(consensus_block_1)
+
+ for _ <- 0..10 do
+ insert(:token_transfer,
+ token_ids: [id],
+ transaction: transaction,
+ token_contract_address: tt.token_contract_address,
+ block_number: consensus_block_1.number,
+ block: consensus_block_1
+ )
+ end
+
+ tt_1 =
+ insert(:token_transfer,
+ token_ids: [id],
+ transaction: transaction,
+ token_contract_address: tt.token_contract_address,
+ block_number: consensus_block_1.number,
+ block: consensus_block_1
+ )
+
+ %{hash: hash_2} = params_for(:block, consensus: true, miner_hash: miner_hash)
+ consensus_block_2 = insert(:block, %{hash: hash_2, number: block_number - 2})
+
+ for _ <- 0..10 do
+ tx =
+ :transaction
+ |> insert()
+ |> with_block(consensus_block_2)
+
+ insert(:token_transfer,
+ token_ids: [id],
+ transaction: tx,
+ token_contract_address: tt.token_contract_address,
+ block_number: consensus_block_2.number,
+ block: consensus_block_2
+ )
+ end
+
+ instance =
+ insert(:token_instance,
+ token_contract_address_hash: token_address.hash,
+ token_id: id,
+ owner_updated_at_block: tt.block_number,
+ owner_updated_at_log_index: tt.log_index,
+ owner_address_hash: insert(:address).hash
+ )
+
+ block_params =
+ params_for(:block, hash: block_hash, miner_hash: miner_hash, number: block_number, consensus: false)
+
+ %Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, block_params)
+ changes_list = [block_changes]
+ error = instance.error
+ block_number = tt_1.block_number
+ log_index = tt_1.log_index
+ owner_address_hash = tt_1.to_address_hash
+ token_address_hash = token_address.hash
+
+ assert {:ok,
+ %{
+ update_token_instances_owner: [
+ %Explorer.Chain.Token.Instance{
+ token_id: ^id,
+ error: ^error,
+ owner_updated_at_block: ^block_number,
+ owner_updated_at_log_index: ^log_index,
+ owner_address_hash: ^owner_address_hash,
+ token_contract_address_hash: ^token_address_hash
+ }
+ ]
+ }} = Multi.new() |> Blocks.run(changes_list, options) |> Repo.transaction()
+ end
+
+ test "change instance owner if there was no more token transfers",
+ %{consensus_block: %{hash: block_hash, miner_hash: miner_hash, number: block_number}, options: options} do
+ block_number = block_number + 1
+ consensus_block = insert(:block, %{hash: block_hash, number: block_number})
+
+ transaction =
+ :transaction
+ |> insert()
+ |> with_block(consensus_block)
+
+ token_address = insert(:contract_address)
+ insert(:token, contract_address: token_address, type: "ERC-721")
+ id = Decimal.new(1)
+
+ tt =
+ insert(:token_transfer,
+ token_ids: [id],
+ transaction: transaction,
+ token_contract_address: token_address,
+ block_number: block_number,
+ block: consensus_block
+ )
+
+ instance =
+ insert(:token_instance,
+ token_contract_address_hash: token_address.hash,
+ token_id: id,
+ owner_updated_at_block: tt.block_number,
+ owner_updated_at_log_index: tt.log_index,
+ owner_address_hash: insert(:address).hash
+ )
+
+ block_params =
+ params_for(:block, hash: block_hash, miner_hash: miner_hash, number: block_number, consensus: false)
+
+ %Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, block_params)
+ changes_list = [block_changes]
+ error = instance.error
+ owner_address_hash = tt.from_address_hash
+ token_address_hash = token_address.hash
+
+ assert {:ok,
+ %{
+ update_token_instances_owner: [
+ %Explorer.Chain.Token.Instance{
+ token_id: ^id,
+ error: ^error,
+ owner_updated_at_block: -1,
+ owner_updated_at_log_index: -1,
+ owner_address_hash: ^owner_address_hash,
+ token_contract_address_hash: ^token_address_hash
+ }
+ ]
+ }} = Multi.new() |> Blocks.run(changes_list, options) |> Repo.transaction()
+ end
end
describe "lose_consensus/5" do
diff --git a/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs b/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs
index caf219b42983..b27bed0ebfa6 100644
--- a/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs
+++ b/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs
@@ -6,7 +6,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do
alias Explorer.Chain.Import.Runner.InternalTransactions
describe "run/1" do
- test "transaction's status becomes :error when its internal_transaction has an error" do
+ test "transaction's status doesn't become :error when its internal_transaction has an error" do
transaction = insert(:transaction) |> with_block(status: :ok)
insert(:pending_block_operation, block_hash: transaction.block_hash, block_number: transaction.block_number)
@@ -19,7 +19,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do
assert {:ok, _} = run_internal_transactions([internal_transaction_changes])
- assert :error == Repo.get(Transaction, transaction.hash).status
+ assert :ok == Repo.get(Transaction, transaction.hash).status
end
test "transaction's has_error_in_internal_txs become true when its internal_transaction (where index != 0) has an error" do
@@ -61,7 +61,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do
assert {:ok, _} = run_internal_transactions([internal_transaction_changes])
tx = Repo.get(Transaction, transaction.hash)
- assert :error == tx.status
+ assert :ok == tx.status
assert false == tx.has_error_in_internal_txs
end
@@ -90,7 +90,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do
assert false == tx.has_error_in_internal_txs
end
- test "simple coin transfer's status becomes :error when its internal_transaction has an error" do
+ test "simple coin transfer's status doesn't become :error when its internal_transaction has an error" do
transaction = insert(:transaction) |> with_block(status: :ok)
insert(:pending_block_operation, block_hash: transaction.block_hash, block_number: transaction.block_number)
@@ -104,10 +104,10 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do
assert {:ok, _} = run_internal_transactions([internal_transaction_changes])
- assert :error == Repo.get(Transaction, transaction.hash).status
+ assert :ok == Repo.get(Transaction, transaction.hash).status
end
- test "for block with 2 simple coin transfer's statuses become :error when its both internal_transactions has an error" do
+ test "for block with 2 simple coin transfer's statuses doesn't become :error even when its both internal_transactions has an error" do
a_block = insert(:block, number: 1000)
transaction1 = insert(:transaction) |> with_block(a_block, status: :ok)
transaction2 = insert(:transaction) |> with_block(a_block, status: :ok)
@@ -128,31 +128,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do
assert {:ok, _} = run_internal_transactions([internal_transaction_changes_1, internal_transaction_changes_2])
- assert :error == Repo.get(Transaction, transaction1.hash).status
- assert :error == Repo.get(Transaction, transaction2.hash).status
- end
-
- test "for block with 2 simple coin transfer's only status become :error for tx where internal_transactions has an error" do
- a_block = insert(:block, number: 1000)
- transaction1 = insert(:transaction) |> with_block(a_block, status: :ok)
- transaction2 = insert(:transaction) |> with_block(a_block, status: :ok)
- insert(:pending_block_operation, block_hash: a_block.hash, block_number: a_block.number)
-
- assert :ok == transaction1.status
- assert :ok == transaction2.status
-
- index = 0
- error = "Out of gas"
-
- internal_transaction_changes_1 =
- make_internal_transaction_changes_for_simple_coin_transfers(transaction1, index, error)
-
- internal_transaction_changes_2 =
- make_internal_transaction_changes_for_simple_coin_transfers(transaction2, index, nil)
-
- assert {:ok, _} = run_internal_transactions([internal_transaction_changes_1, internal_transaction_changes_2])
-
- assert :error == Repo.get(Transaction, transaction1.hash).status
+ assert :ok == Repo.get(Transaction, transaction1.hash).status
assert :ok == Repo.get(Transaction, transaction2.hash).status
end
@@ -329,6 +305,57 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do
assert %{consensus: true} = Repo.get(Block, full_block.hash)
assert PendingBlockOperation |> Repo.get(full_block.hash) |> is_nil()
end
+
+ test "does not remove consensus from non-traceable blocks" do
+ original_config = Application.get_env(:indexer, :trace_block_ranges)
+
+ full_block = insert(:block)
+ transaction_a = insert(:transaction) |> with_block(full_block)
+ transaction_b = insert(:transaction) |> with_block(full_block)
+
+ Application.put_env(:indexer, :trace_block_ranges, "#{full_block.number + 1}..latest")
+
+ insert(:pending_block_operation, block_hash: full_block.hash, block_number: full_block.number)
+
+ transaction_a_changes = make_internal_transaction_changes(transaction_a, 0, nil)
+
+ assert {:ok, _} = run_internal_transactions([transaction_a_changes])
+
+ assert from(i in InternalTransaction, where: i.transaction_hash == ^transaction_a.hash) |> Repo.one() |> is_nil()
+ assert from(i in InternalTransaction, where: i.transaction_hash == ^transaction_b.hash) |> Repo.one() |> is_nil()
+
+ assert %{consensus: true} = Repo.get(Block, full_block.hash)
+
+ on_exit(fn -> Application.put_env(:indexer, :trace_block_ranges, original_config) end)
+ end
+
+ test "successfully imports internal transaction with stop type" do
+ block = insert(:block)
+ transaction = insert(:transaction) |> with_block(block, status: :ok)
+ insert(:pending_block_operation, block_hash: transaction.block_hash, block_number: transaction.block_number)
+
+ assert :ok == transaction.status
+
+ {:ok, from_address_hash} = Explorer.Chain.Hash.Address.cast("0x0000000000000000000000000000000000000000")
+ {:ok, input} = Explorer.Chain.Data.cast("0x")
+
+ internal_transaction_changes = %{
+ block_number: block.number,
+ error: "execution stopped",
+ from_address_hash: from_address_hash,
+ gas: 0,
+ gas_used: 22594,
+ index: 0,
+ input: input,
+ trace_address: [],
+ transaction_hash: transaction.hash,
+ transaction_index: 0,
+ type: :stop,
+ value: Wei.from(Decimal.new(0), :wei)
+ }
+
+ assert {:ok, _} = run_internal_transactions([internal_transaction_changes])
+ end
end
defp run_internal_transactions(changes_list, multi \\ Multi.new()) when is_list(changes_list) do
diff --git a/apps/explorer/test/explorer/chain/import/runner/transactions_test.exs b/apps/explorer/test/explorer/chain/import/runner/transactions_test.exs
index 2bfea8750b0f..a6ab5af0a21e 100644
--- a/apps/explorer/test/explorer/chain/import/runner/transactions_test.exs
+++ b/apps/explorer/test/explorer/chain/import/runner/transactions_test.exs
@@ -2,7 +2,7 @@ defmodule Explorer.Chain.Import.Runner.TransactionsTest do
use Explorer.DataCase
alias Ecto.Multi
- alias Explorer.Chain.{Address, Transaction}
+ alias Explorer.Chain.{Address, Block, Transaction}
alias Explorer.Chain.Import.Runner.Transactions
describe "run/1" do
@@ -37,6 +37,47 @@ defmodule Explorer.Chain.Import.Runner.TransactionsTest do
assert is_nil(Repo.get(Transaction, transaction.hash).created_contract_code_indexed_at)
end
+
+ test "recollated transactions replaced with empty data" do
+ reorg = insert(:block)
+ reorg_transaction = :transaction |> insert() |> with_block(reorg)
+ transaction = :transaction |> insert() |> with_block(reorg)
+ reorg |> Block.changeset(%{consensus: false}) |> Repo.update()
+ block = insert(:block, consensus: true, number: reorg.number)
+
+ transaction_params = %{
+ block_hash: block.hash,
+ block_number: block.number,
+ gas_used: transaction.gas_used,
+ cumulative_gas_used: transaction.cumulative_gas_used,
+ index: transaction.index,
+ from_address_hash: transaction.from_address.hash,
+ gas: transaction.gas,
+ gas_price: transaction.gas_price,
+ hash: transaction.hash,
+ input: transaction.input,
+ nonce: transaction.nonce,
+ r: transaction.r,
+ s: transaction.s,
+ to_address_hash: transaction.to_address.hash,
+ v: transaction.v,
+ value: transaction.value
+ }
+
+ assert {:ok, _} = run_transactions([transaction_params])
+
+ assert %{
+ block_hash: nil,
+ block_number: nil,
+ gas_used: nil,
+ cumulative_gas_used: nil,
+ index: nil,
+ status: nil,
+ error: nil
+ } = Repo.get_by(Transaction, hash: reorg_transaction.hash)
+
+ assert not is_nil(Repo.get_by(Transaction, hash: transaction.hash).block_hash)
+ end
end
defp run_transactions(changes_list) when is_list(changes_list) do
diff --git a/apps/explorer/test/explorer/chain/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs
index 8078cb33dd9f..0d2fbfa02de3 100644
--- a/apps/explorer/test/explorer/chain/import_test.exs
+++ b/apps/explorer/test/explorer/chain/import_test.exs
@@ -22,9 +22,16 @@ defmodule Explorer.Chain.ImportTest do
@moduletag :capturelog
+ @first_topic_hex_string "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
+ @second_topic_hex_string "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"
+ @third_topic_hex_string "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d"
+
doctest Import
describe "all/1" do
+ {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(@first_topic_hex_string)
+ {:ok, second_topic} = Explorer.Chain.Hash.Full.cast(@second_topic_hex_string)
+ {:ok, third_topic} = Explorer.Chain.Hash.Full.cast(@third_topic_hex_string)
# set :timeout options to cover lines that use the timeout override when available
@import_data %{
blocks: %{
@@ -91,13 +98,12 @@ defmodule Explorer.Chain.ImportTest do
block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000",
- first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
- second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
- third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d",
+ first_topic: first_topic,
+ second_topic: second_topic,
+ third_topic: third_topic,
fourth_topic: nil,
index: 0,
- transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
- type: "mined"
+ transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5"
}
],
timeout: 5
@@ -165,6 +171,9 @@ defmodule Explorer.Chain.ImportTest do
}
test "with valid data" do
+ {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(@first_topic_hex_string)
+ {:ok, second_topic} = Explorer.Chain.Hash.Full.cast(@second_topic_hex_string)
+ {:ok, third_topic} = Explorer.Chain.Hash.Full.cast(@third_topic_hex_string)
difficulty = Decimal.new(340_282_366_920_938_463_463_374_607_431_768_211_454)
total_difficulty = Decimal.new(12_590_447_576_074_723_148_144_860_474_975_121_280_509)
token_transfer_amount = Decimal.new(1_000_000_000_000_000_000)
@@ -276,9 +285,9 @@ defmodule Explorer.Chain.ImportTest do
167, 100, 0, 0>>
},
index: 0,
- first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
- second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
- third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d",
+ first_topic: ^first_topic,
+ second_topic: ^second_topic,
+ third_topic: ^third_topic,
fourth_topic: nil,
transaction_hash: %Hash{
byte_count: 32,
@@ -286,7 +295,6 @@ defmodule Explorer.Chain.ImportTest do
<<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57,
101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>>
},
- type: "mined",
inserted_at: %{},
updated_at: %{}
}
@@ -350,6 +358,19 @@ defmodule Explorer.Chain.ImportTest do
}} = Import.all(@import_data)
end
+ test "block consensus removed if there was an exception in further steps" do
+ not_existing_block_hash = "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471db"
+
+ incorrect_data =
+ update_in(@import_data, [:transactions, :params], fn params ->
+ [params |> Enum.at(0) |> Map.put(:block_hash, not_existing_block_hash)]
+ end)
+
+ assert_raise(Postgrex.Error, fn -> Import.all(incorrect_data) end)
+ assert [] = Repo.all(Transaction)
+ assert %{consensus: false} = Repo.one(Block)
+ end
+
test "inserts a token_balance" do
params = %{
addresses: %{
diff --git a/apps/explorer/test/explorer/chain/internal_transaction_test.exs b/apps/explorer/test/explorer/chain/internal_transaction_test.exs
index 54a1b9f5b164..96b66e9b7e2d 100644
--- a/apps/explorer/test/explorer/chain/internal_transaction_test.exs
+++ b/apps/explorer/test/explorer/chain/internal_transaction_test.exs
@@ -58,6 +58,30 @@ defmodule Explorer.Chain.InternalTransactionTest do
assert Repo.insert(changeset)
end
+
+ test "with stop type" do
+ transaction = insert(:transaction)
+
+ changeset =
+ InternalTransaction.changeset(%InternalTransaction{}, %{
+ from_address_hash: "0x0000000000000000000000000000000000000000",
+ gas: 0,
+ gas_used: 22234,
+ index: 0,
+ input: "0x",
+ trace_address: [],
+ transaction_hash: transaction.hash,
+ transaction_index: 0,
+ type: "stop",
+ error: "execution stopped",
+ value: 0,
+ block_number: 35,
+ block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
+ block_index: 0
+ })
+
+ assert changeset.valid?
+ end
end
defp call_type(opts) do
diff --git a/apps/explorer/test/explorer/chain/log_test.exs b/apps/explorer/test/explorer/chain/log_test.exs
index 3da55a86c14a..87881776de29 100644
--- a/apps/explorer/test/explorer/chain/log_test.exs
+++ b/apps/explorer/test/explorer/chain/log_test.exs
@@ -7,10 +7,19 @@ defmodule Explorer.Chain.LogTest do
alias Explorer.Chain.{Log, SmartContract}
alias Explorer.Repo
+ @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65"
+
+ defp topic(topic_hex_string) do
+ {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string)
+ topic
+ end
+
setup :set_mox_from_context
doctest Log
+ setup :verify_on_exit!
+
describe "changeset/2" do
test "accepts valid attributes" do
params =
@@ -34,18 +43,21 @@ defmodule Explorer.Chain.LogTest do
params_for(
:log,
address_hash: build(:address).hash,
- first_topic: "ham",
+ first_topic: @first_topic_hex_string_1,
transaction_hash: build(:transaction).hash,
block_hash: build(:block).hash
)
- assert %Changeset{changes: %{first_topic: "ham"}, valid?: true} = Log.changeset(%Log{}, params)
+ result = Log.changeset(%Log{}, params)
+
+ assert result.valid? == true
+ assert result.changes.first_topic == topic(@first_topic_hex_string_1)
end
test "assigns optional attributes" do
- params = Map.put(params_for(:log), :first_topic, "ham")
+ params = Map.put(params_for(:log), :first_topic, topic(@first_topic_hex_string_1))
changeset = Log.changeset(%Log{}, params)
- assert changeset.changes.first_topic === "ham"
+ assert changeset.changes.first_topic === topic(@first_topic_hex_string_1)
end
end
@@ -58,7 +70,7 @@ defmodule Explorer.Chain.LogTest do
log = insert(:log, transaction: transaction)
- assert Log.decode(log, transaction) == {:error, :could_not_decode}
+ assert {{:error, :could_not_decode}, _, _} = Log.decode(log, transaction, [], false)
end
test "that a contract call transaction that has a verified contract returns the decoded input data" do
@@ -97,25 +109,24 @@ defmodule Explorer.Chain.LogTest do
insert(:log,
address: to_address,
transaction: transaction,
- first_topic: topic1,
- second_topic: topic2,
- third_topic: topic3,
+ first_topic: topic(topic1),
+ second_topic: topic(topic2),
+ third_topic: topic(topic3),
fourth_topic: nil,
data: data
)
- get_eip1967_implementation()
-
- assert Log.decode(log, transaction) ==
- {:ok, "eb9b3c4c", "WantsPets(string indexed _from_human, uint256 _number, bool indexed _belly)",
- [
- {"_from_human", "string", true,
- {:dynamic,
- <<56, 228, 122, 123, 113, 157, 206, 99, 102, 42, 234, 244, 52, 64, 50, 111, 85, 27, 138, 126, 225,
- 152, 206, 227, 92, 181, 213, 23, 242, 210, 150, 162>>}},
- {"_number", "uint256", false, 0},
- {"_belly", "bool", true, true}
- ]}
+ request_zero_implementations()
+
+ assert {{:ok, "eb9b3c4c", "WantsPets(string indexed _from_human, uint256 _number, bool indexed _belly)",
+ [
+ {"_from_human", "string", true,
+ {:dynamic,
+ <<56, 228, 122, 123, 113, 157, 206, 99, 102, 42, 234, 244, 52, 64, 50, 111, 85, 27, 138, 126, 225,
+ 152, 206, 227, 92, 181, 213, 23, 242, 210, 150, 162>>}},
+ {"_number", "uint256", false, 0},
+ {"_belly", "bool", true, true}
+ ]}, _, _} = Log.decode(log, transaction, [], false)
end
test "finds decoding candidates" do
@@ -152,30 +163,29 @@ defmodule Explorer.Chain.LogTest do
log =
insert(:log,
transaction: transaction,
- first_topic: topic1,
- second_topic: topic2,
- third_topic: topic3,
+ first_topic: topic(topic1),
+ second_topic: topic(topic2),
+ third_topic: topic(topic3),
fourth_topic: nil,
data: data
)
- assert Log.decode(log, transaction) ==
- {:error, :contract_not_verified,
- [
- {:ok, "eb9b3c4c", "WantsPets(string indexed _from_human, uint256 _number, bool indexed _belly)",
- [
- {"_from_human", "string", true,
- {:dynamic,
- <<56, 228, 122, 123, 113, 157, 206, 99, 102, 42, 234, 244, 52, 64, 50, 111, 85, 27, 138, 126,
- 225, 152, 206, 227, 92, 181, 213, 23, 242, 210, 150, 162>>}},
- {"_number", "uint256", false, 0},
- {"_belly", "bool", true, true}
- ]}
- ]}
+ assert {{:error, :contract_not_verified,
+ [
+ {:ok, "eb9b3c4c", "WantsPets(string indexed _from_human, uint256 _number, bool indexed _belly)",
+ [
+ {"_from_human", "string", true,
+ {:dynamic,
+ <<56, 228, 122, 123, 113, 157, 206, 99, 102, 42, 234, 244, 52, 64, 50, 111, 85, 27, 138, 126, 225,
+ 152, 206, 227, 92, 181, 213, 23, 242, 210, 150, 162>>}},
+ {"_number", "uint256", false, 0},
+ {"_belly", "bool", true, true}
+ ]}
+ ]}, _, _} = Log.decode(log, transaction, [], false)
end
end
- def get_eip1967_implementation do
+ def request_zero_implementations do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
@@ -213,5 +223,17 @@ defmodule Explorer.Chain.LogTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
end
end
diff --git a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs
new file mode 100644
index 000000000000..f5fd83b562a2
--- /dev/null
+++ b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs
@@ -0,0 +1,469 @@
+defmodule Explorer.Chain.SmartContract.ProxyTest do
+ use Explorer.DataCase, async: false
+ import Mox
+ alias Explorer.Chain.SmartContract
+ alias Explorer.Chain.SmartContract.Proxy
+
+ setup :verify_on_exit!
+ setup :set_mox_global
+
+ describe "proxy contracts features" do
+ @proxy_abi [
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [%{"type" => "bool", "name" => ""}],
+ "name" => "upgradeTo",
+ "inputs" => [%{"type" => "address", "name" => "newImplementation"}],
+ "constant" => false
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "uint256", "name" => ""}],
+ "name" => "version",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "address", "name" => ""}],
+ "name" => "implementation",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [],
+ "name" => "renounceOwnership",
+ "inputs" => [],
+ "constant" => false
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "address", "name" => ""}],
+ "name" => "getOwner",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "address", "name" => ""}],
+ "name" => "getProxyStorage",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [],
+ "name" => "transferOwnership",
+ "inputs" => [%{"type" => "address", "name" => "_newOwner"}],
+ "constant" => false
+ },
+ %{
+ "type" => "constructor",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "inputs" => [
+ %{"type" => "address", "name" => "_proxyStorage"},
+ %{"type" => "address", "name" => "_implementationAddress"}
+ ]
+ },
+ %{"type" => "fallback", "stateMutability" => "nonpayable", "payable" => false},
+ %{
+ "type" => "event",
+ "name" => "Upgraded",
+ "inputs" => [
+ %{"type" => "uint256", "name" => "version", "indexed" => false},
+ %{"type" => "address", "name" => "implementation", "indexed" => true}
+ ],
+ "anonymous" => false
+ },
+ %{
+ "type" => "event",
+ "name" => "OwnershipRenounced",
+ "inputs" => [%{"type" => "address", "name" => "previousOwner", "indexed" => true}],
+ "anonymous" => false
+ },
+ %{
+ "type" => "event",
+ "name" => "OwnershipTransferred",
+ "inputs" => [
+ %{"type" => "address", "name" => "previousOwner", "indexed" => true},
+ %{"type" => "address", "name" => "newOwner", "indexed" => true}
+ ],
+ "anonymous" => false
+ }
+ ]
+
+ @implementation_abi [
+ %{
+ "constant" => false,
+ "inputs" => [%{"name" => "x", "type" => "uint256"}],
+ "name" => "set",
+ "outputs" => [],
+ "payable" => false,
+ "stateMutability" => "nonpayable",
+ "type" => "function"
+ },
+ %{
+ "constant" => true,
+ "inputs" => [],
+ "name" => "get",
+ "outputs" => [%{"name" => "", "type" => "uint256"}],
+ "payable" => false,
+ "stateMutability" => "view",
+ "type" => "function"
+ }
+ ]
+
+ defp request_EIP1967_zero_implementations do
+ EthereumJSONRPC.Mox
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
+ end
+
+ # EIP-1967 + EIP-1822
+ defp request_zero_implementations do
+ request_EIP1967_zero_implementations()
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
+ end
+
+ test "combine_proxy_implementation_abi/2 returns empty [] abi if proxy abi is null" do
+ proxy_contract_address = insert(:contract_address)
+
+ assert Proxy.combine_proxy_implementation_abi(%SmartContract{address_hash: proxy_contract_address.hash, abi: nil}) ==
+ []
+ end
+
+ test "combine_proxy_implementation_abi/2 returns [] abi for unverified proxy" do
+ proxy_contract_address = insert(:contract_address)
+
+ smart_contract =
+ insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123")
+
+ request_zero_implementations()
+
+ assert Proxy.combine_proxy_implementation_abi(smart_contract) == []
+ end
+
+ test "combine_proxy_implementation_abi/2 returns proxy abi if implementation is not verified" do
+ proxy_contract_address = insert(:contract_address)
+
+ smart_contract =
+ insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
+
+ request_zero_implementations()
+
+ assert Proxy.combine_proxy_implementation_abi(smart_contract) == @proxy_abi
+ end
+
+ test "combine_proxy_implementation_abi/2 returns proxy + implementation abi if implementation is verified" do
+ proxy_contract_address = insert(:contract_address)
+
+ smart_contract =
+ insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
+
+ implementation_contract_address = insert(:contract_address)
+
+ insert(:smart_contract,
+ address_hash: implementation_contract_address.hash,
+ abi: @implementation_abi,
+ contract_code_md5: "123"
+ )
+
+ implementation_contract_address_hash_string =
+ Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
+
+ request_zero_implementations()
+
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
+ {:ok,
+ [
+ %{
+ id: id,
+ jsonrpc: "2.0",
+ result: "0x000000000000000000000000" <> implementation_contract_address_hash_string
+ }
+ ]}
+ end
+ )
+
+ combined_abi = Proxy.combine_proxy_implementation_abi(smart_contract)
+
+ assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == false
+ assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == false
+ assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == true
+ assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == true
+ end
+
+ test "get_implementation_abi_from_proxy/2 returns empty [] abi if proxy abi is null" do
+ proxy_contract_address = insert(:contract_address)
+
+ assert Proxy.get_implementation_abi_from_proxy(
+ %SmartContract{address_hash: proxy_contract_address.hash, abi: nil},
+ []
+ ) ==
+ []
+ end
+
+ test "get_implementation_abi_from_proxy/2 returns [] abi for unverified proxy" do
+ proxy_contract_address = insert(:contract_address)
+
+ smart_contract =
+ insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123")
+
+ request_zero_implementations()
+
+ assert Proxy.combine_proxy_implementation_abi(smart_contract) == []
+ end
+
+ test "get_implementation_abi_from_proxy/2 returns [] if implementation is not verified" do
+ proxy_contract_address = insert(:contract_address)
+
+ smart_contract =
+ insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
+
+ request_zero_implementations()
+
+ assert Proxy.get_implementation_abi_from_proxy(smart_contract, []) == []
+ end
+
+ test "get_implementation_abi_from_proxy/2 returns implementation abi if implementation is verified" do
+ proxy_contract_address = insert(:contract_address)
+
+ smart_contract =
+ insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
+
+ implementation_contract_address = insert(:contract_address)
+
+ insert(:smart_contract,
+ address_hash: implementation_contract_address.hash,
+ abi: @implementation_abi,
+ contract_code_md5: "123"
+ )
+
+ implementation_contract_address_hash_string =
+ Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
+
+ request_zero_implementations()
+
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
+ {:ok,
+ [
+ %{
+ id: id,
+ jsonrpc: "2.0",
+ result: "0x000000000000000000000000" <> implementation_contract_address_hash_string
+ }
+ ]}
+ end
+ )
+
+ implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, [])
+
+ assert implementation_abi == @implementation_abi
+ end
+
+ test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern (logic contract)" do
+ proxy_contract_address = insert(:contract_address)
+
+ smart_contract =
+ insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123")
+
+ implementation_contract_address = insert(:contract_address)
+
+ insert(:smart_contract,
+ address_hash: implementation_contract_address.hash,
+ abi: @implementation_abi,
+ contract_code_md5: "123"
+ )
+
+ implementation_contract_address_hash_string =
+ Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
+
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn %{
+ id: _id,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x000000000000000000000000" <> implementation_contract_address_hash_string}
+ end
+ )
+
+ implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, [])
+
+ assert implementation_abi == @implementation_abi
+ end
+ end
+
+ @beacon_abi [
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}],
+ "name" => "implementation",
+ "inputs" => []
+ }
+ ]
+ test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern (beacon contract)" do
+ proxy_contract_address = insert(:contract_address)
+
+ smart_contract =
+ insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123")
+
+ beacon_contract_address = insert(:contract_address)
+
+ insert(:smart_contract,
+ address_hash: beacon_contract_address.hash,
+ abi: @beacon_abi,
+ contract_code_md5: "123"
+ )
+
+ beacon_contract_address_hash_string = Base.encode16(beacon_contract_address.hash.bytes, case: :lower)
+
+ implementation_contract_address = insert(:contract_address)
+
+ insert(:smart_contract,
+ address_hash: implementation_contract_address.hash,
+ abi: @implementation_abi,
+ contract_code_md5: "123"
+ )
+
+ implementation_contract_address_hash_string =
+ Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
+
+ EthereumJSONRPC.Mox
+ |> expect(
+ :json_rpc,
+ fn %{
+ id: _id,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end
+ )
+ |> expect(
+ :json_rpc,
+ fn %{
+ id: _id,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x000000000000000000000000" <> beacon_contract_address_hash_string}
+ end
+ )
+ |> expect(
+ :json_rpc,
+ fn [
+ %{
+ id: _id,
+ method: "eth_call",
+ params: [
+ %{data: "0x5c60da1b", to: "0x000000000000000000000000" <> ^beacon_contract_address_hash_string},
+ "latest"
+ ]
+ }
+ ],
+ _options ->
+ {
+ :ok,
+ [
+ %{
+ id: _id,
+ jsonrpc: "2.0",
+ result: "0x000000000000000000000000" <> implementation_contract_address_hash_string
+ }
+ ]
+ }
+ end
+ )
+
+ implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, [])
+ verify!(EthereumJSONRPC.Mox)
+
+ assert implementation_abi == @implementation_abi
+ end
+end
diff --git a/apps/explorer/test/explorer/chain/smart_contract_test.exs b/apps/explorer/test/explorer/chain/smart_contract_test.exs
index aa2b90cec982..1501d08859cd 100644
--- a/apps/explorer/test/explorer/chain/smart_contract_test.exs
+++ b/apps/explorer/test/explorer/chain/smart_contract_test.exs
@@ -3,7 +3,8 @@ defmodule Explorer.Chain.SmartContractTest do
import Mox
alias Explorer.Chain
- alias Explorer.Chain.SmartContract
+ alias Explorer.Chain.{Address, SmartContract}
+ alias Explorer.Chain.SmartContract.Proxy
doctest Explorer.Chain.SmartContract
@@ -14,47 +15,65 @@ defmodule Explorer.Chain.SmartContractTest do
test "check proxy_contract/1 function" do
smart_contract = insert(:smart_contract)
- Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20))
- Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20))
+ proxy =
+ :explorer
+ |> Application.get_env(:proxy)
+ |> Keyword.replace(:fallback_cached_implementation_data_ttl, :timer.seconds(20))
+ |> Keyword.replace(:implementation_data_fetching_timeout, :timer.seconds(20))
+
+ Application.put_env(:explorer, :proxy, proxy)
refute smart_contract.implementation_fetched_at
- # fetch nil implementation and save it to db
+ # fetch nil implementation and don't save it to db
get_eip1967_implementation_zero_addresses()
- refute SmartContract.proxy_contract?(smart_contract)
- verify!(EthereumJSONRPC.Mox)
- assert_empty_implementation(smart_contract.address_hash)
- # extract proxy info from db
- refute SmartContract.proxy_contract?(smart_contract)
+ refute Proxy.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
- assert_empty_implementation(smart_contract.address_hash)
+ assert_implementation_never_fetched(smart_contract.address_hash)
+
+ proxy =
+ :explorer
+ |> Application.get_env(:proxy)
+ |> Keyword.replace(:fallback_cached_implementation_data_ttl, 0)
- Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0)
+ Application.put_env(:explorer, :proxy, proxy)
get_eip1967_implementation_error_response()
- refute SmartContract.proxy_contract?(smart_contract)
+ refute Proxy.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
get_eip1967_implementation_non_zero_address()
- assert SmartContract.proxy_contract?(smart_contract)
+ assert Proxy.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
assert_implementation_address(smart_contract.address_hash)
get_eip1967_implementation_non_zero_address()
- assert SmartContract.proxy_contract?(smart_contract)
+ assert Proxy.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
assert_implementation_address(smart_contract.address_hash)
- Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20))
- assert SmartContract.proxy_contract?(smart_contract)
+ proxy =
+ :explorer
+ |> Application.get_env(:proxy)
+ |> Keyword.replace(:fallback_cached_implementation_data_ttl, :timer.seconds(20))
+
+ Application.put_env(:explorer, :proxy, proxy)
+
+ assert Proxy.proxy_contract?(smart_contract)
+
+ proxy =
+ :explorer
+ |> Application.get_env(:proxy)
+ |> Keyword.replace(:fallback_cached_implementation_data_ttl, 0)
+
+ Application.put_env(:explorer, :proxy, proxy)
- Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0)
get_eip1967_implementation_non_zero_address()
- assert SmartContract.proxy_contract?(smart_contract)
+ assert Proxy.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
get_eip1967_implementation_error_response()
- assert SmartContract.proxy_contract?(smart_contract)
+ assert Proxy.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
end
@@ -62,35 +81,44 @@ defmodule Explorer.Chain.SmartContractTest do
smart_contract = insert(:smart_contract)
implementation_smart_contract = insert(:smart_contract, name: "proxy")
- Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20))
- Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20))
+ proxy =
+ :explorer
+ |> Application.get_env(:proxy)
+ |> Keyword.replace(:fallback_cached_implementation_data_ttl, :timer.seconds(20))
+ |> Keyword.replace(:implementation_data_fetching_timeout, :timer.seconds(20))
+
+ Application.put_env(:explorer, :proxy, proxy)
refute smart_contract.implementation_fetched_at
- # fetch nil implementation and save it to db
+ # fetch nil implementation and don't save it to db
get_eip1967_implementation_zero_addresses()
assert {nil, nil} = SmartContract.get_implementation_address_hash(smart_contract)
verify!(EthereumJSONRPC.Mox)
- assert_empty_implementation(smart_contract.address_hash)
+ assert_implementation_never_fetched(smart_contract.address_hash)
# extract proxy info from db
- assert {nil, nil} = SmartContract.get_implementation_address_hash(smart_contract)
- assert_empty_implementation(smart_contract.address_hash)
+ proxy =
+ :explorer
+ |> Application.get_env(:proxy)
+ |> Keyword.replace(:fallback_cached_implementation_data_ttl, 0)
- Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0)
+ Application.put_env(:explorer, :proxy, proxy)
string_implementation_address_hash = to_string(implementation_smart_contract.address_hash)
- expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
- id: 0,
- method: "eth_getStorageAt",
- params: [
- _,
- "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
- "latest"
- ]
- },
- _options ->
+ mock_empty_logic_storage_pointer_request()
+ |> mock_empty_beacon_storage_pointer_request()
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3",
+ "latest"
+ ]
+ },
+ _options ->
{:ok, string_implementation_address_hash}
end)
@@ -118,23 +146,38 @@ defmodule Explorer.Chain.SmartContractTest do
implementation_smart_contract.name
)
- contract_1 = Chain.address_hash_to_smart_contract(smart_contract.address_hash)
+ contract_1 = SmartContract.address_hash_to_smart_contract(smart_contract.address_hash)
+
+ proxy =
+ :explorer
+ |> Application.get_env(:proxy)
+ |> Keyword.replace(:fallback_cached_implementation_data_ttl, :timer.seconds(20))
- Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20))
+ Application.put_env(:explorer, :proxy, proxy)
assert {^string_implementation_address_hash, "proxy"} =
SmartContract.get_implementation_address_hash(smart_contract)
- contract_2 = Chain.address_hash_to_smart_contract(smart_contract.address_hash)
+ contract_2 = SmartContract.address_hash_to_smart_contract(smart_contract.address_hash)
assert contract_1.implementation_fetched_at == contract_2.implementation_fetched_at &&
contract_1.updated_at == contract_2.updated_at
- Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0)
+ proxy =
+ :explorer
+ |> Application.get_env(:proxy)
+ |> Keyword.replace(:fallback_cached_implementation_data_ttl, 0)
+
+ Application.put_env(:explorer, :proxy, proxy)
get_eip1967_implementation_zero_addresses()
- assert {nil, nil} = SmartContract.get_implementation_address_hash(smart_contract)
+
+ assert {^string_implementation_address_hash, "proxy"} =
+ SmartContract.get_implementation_address_hash(smart_contract)
+
verify!(EthereumJSONRPC.Mox)
- assert_empty_implementation(smart_contract.address_hash)
+
+ assert contract_1.implementation_fetched_at == contract_2.implementation_fetched_at &&
+ contract_1.updated_at == contract_2.updated_at
end
test "test get_implementation_address_hash/1 for twins contract" do
@@ -143,11 +186,16 @@ defmodule Explorer.Chain.SmartContractTest do
smart_contract = insert(:smart_contract)
another_address = insert(:contract_address)
- twin = Chain.address_hash_to_smart_contract(another_address.hash)
+ twin = SmartContract.address_hash_to_smart_contract(another_address.hash)
implementation_smart_contract = insert(:smart_contract, name: "proxy")
- Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20))
- Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20))
+ proxy =
+ :explorer
+ |> Application.get_env(:proxy)
+ |> Keyword.replace(:fallback_cached_implementation_data_ttl, :timer.seconds(20))
+ |> Keyword.replace(:implementation_data_fetching_timeout, :timer.seconds(20))
+
+ Application.put_env(:explorer, :proxy, proxy)
# fetch nil implementation
get_eip1967_implementation_zero_addresses()
@@ -162,18 +210,7 @@ defmodule Explorer.Chain.SmartContractTest do
string_implementation_address_hash = to_string(implementation_smart_contract.address_hash)
- expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
- id: 0,
- method: "eth_getStorageAt",
- params: [
- _,
- "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
- "latest"
- ]
- },
- _options ->
- {:ok, string_implementation_address_hash}
- end)
+ expect_address_in_response(string_implementation_address_hash)
assert {^string_implementation_address_hash, "proxy"} = SmartContract.get_implementation_address_hash(twin)
@@ -194,8 +231,13 @@ defmodule Explorer.Chain.SmartContractTest do
implementation_smart_contract = insert(:smart_contract, name: "proxy")
- Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20))
- Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20))
+ proxy =
+ :explorer
+ |> Application.get_env(:proxy)
+ |> Keyword.replace(:fallback_cached_implementation_data_ttl, :timer.seconds(20))
+ |> Keyword.replace(:implementation_data_fetching_timeout, :timer.seconds(20))
+
+ Application.put_env(:explorer, :proxy, proxy)
# fetch nil implementation
get_eip1967_implementation_zero_addresses()
@@ -210,18 +252,7 @@ defmodule Explorer.Chain.SmartContractTest do
string_implementation_address_hash = to_string(implementation_smart_contract.address_hash)
- expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
- id: 0,
- method: "eth_getStorageAt",
- params: [
- _,
- "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
- "latest"
- ]
- },
- _options ->
- {:ok, string_implementation_address_hash}
- end)
+ expect_address_in_response(string_implementation_address_hash)
assert {^string_implementation_address_hash, "proxy"} = SmartContract.get_implementation_address_hash(twin)
@@ -252,8 +283,13 @@ defmodule Explorer.Chain.SmartContractTest do
implementation_smart_contract = insert(:smart_contract, name: "proxy")
- Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20))
- Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20))
+ proxy =
+ :explorer
+ |> Application.get_env(:proxy)
+ |> Keyword.replace(:fallback_cached_implementation_data_ttl, :timer.seconds(20))
+ |> Keyword.replace(:implementation_data_fetching_timeout, :timer.seconds(20))
+
+ Application.put_env(:explorer, :proxy, proxy)
# fetch nil implementation
get_eip1967_implementation_zero_addresses()
@@ -268,18 +304,7 @@ defmodule Explorer.Chain.SmartContractTest do
string_implementation_address_hash = to_string(implementation_smart_contract.address_hash)
- expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
- id: 0,
- method: "eth_getStorageAt",
- params: [
- _,
- "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
- "latest"
- ]
- },
- _options ->
- {:ok, string_implementation_address_hash}
- end)
+ expect_address_in_response(string_implementation_address_hash)
assert {^string_implementation_address_hash, "proxy"} = SmartContract.get_implementation_address_hash(twin)
@@ -298,31 +323,15 @@ defmodule Explorer.Chain.SmartContractTest do
end
def get_eip1967_implementation_zero_addresses do
- EthereumJSONRPC.Mox
- |> expect(:json_rpc, fn %{
- id: 0,
- method: "eth_getStorageAt",
- params: [
- _,
- "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
- "latest"
- ]
- },
- _options ->
- {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
- end)
- |> expect(:json_rpc, fn %{
- id: 0,
- method: "eth_getStorageAt",
- params: [
- _,
- "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50",
- "latest"
- ]
- },
- _options ->
- {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
- end)
+ mock_empty_logic_storage_pointer_request()
+ |> mock_empty_beacon_storage_pointer_request()
+ |> mock_empty_oz_storage_pointer_request()
+ |> mock_empty_eip_1822_storage_pointer_request()
+ end
+
+ def get_eip1967_implementation_non_zero_address do
+ mock_empty_logic_storage_pointer_request()
+ |> mock_empty_beacon_storage_pointer_request()
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
@@ -333,21 +342,6 @@ defmodule Explorer.Chain.SmartContractTest do
]
},
_options ->
- {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
- end)
- end
-
- def get_eip1967_implementation_non_zero_address do
- expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
- id: 0,
- method: "eth_getStorageAt",
- params: [
- _,
- "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
- "latest"
- ]
- },
- _options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000001"}
end)
end
@@ -366,38 +360,539 @@ defmodule Explorer.Chain.SmartContractTest do
_options ->
{:error, "error"}
end)
+ |> mock_empty_beacon_storage_pointer_request()
+ |> mock_empty_oz_storage_pointer_request()
+ |> mock_empty_eip_1822_storage_pointer_request()
end
def assert_empty_implementation(address_hash) do
- contract = Chain.address_hash_to_smart_contract(address_hash)
+ contract = SmartContract.address_hash_to_smart_contract(address_hash)
assert contract.implementation_fetched_at
refute contract.implementation_name
refute contract.implementation_address_hash
end
def assert_implementation_never_fetched(address_hash) do
- contract = Chain.address_hash_to_smart_contract(address_hash)
+ contract = SmartContract.address_hash_to_smart_contract(address_hash)
refute contract.implementation_fetched_at
refute contract.implementation_name
refute contract.implementation_address_hash
end
def assert_implementation_address(address_hash) do
- contract = Chain.address_hash_to_smart_contract(address_hash)
+ contract = SmartContract.address_hash_to_smart_contract(address_hash)
assert contract.implementation_fetched_at
assert contract.implementation_address_hash
end
def assert_implementation_name(address_hash) do
- contract = Chain.address_hash_to_smart_contract(address_hash)
+ contract = SmartContract.address_hash_to_smart_contract(address_hash)
assert contract.implementation_fetched_at
assert contract.implementation_name
end
def assert_exact_name_and_address(address_hash, implementation_address_hash, implementation_name) do
- contract = Chain.address_hash_to_smart_contract(address_hash)
+ contract = SmartContract.address_hash_to_smart_contract(address_hash)
assert contract.implementation_fetched_at
assert contract.implementation_name == implementation_name
assert to_string(contract.implementation_address_hash) == to_string(implementation_address_hash)
end
+
+ describe "create_smart_contract/1" do
+ setup do
+ smart_contract_bytecode =
+ "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029"
+
+ created_contract_address =
+ insert(
+ :address,
+ hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
+ contract_code: smart_contract_bytecode
+ )
+
+ transaction =
+ :transaction
+ |> insert()
+ |> with_block()
+
+ insert(
+ :internal_transaction_create,
+ transaction: transaction,
+ index: 0,
+ created_contract_address: created_contract_address,
+ created_contract_code: smart_contract_bytecode,
+ block_number: transaction.block_number,
+ block_hash: transaction.block_hash,
+ block_index: 0,
+ transaction_index: transaction.index
+ )
+
+ valid_attrs = %{
+ address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
+ name: "SimpleStorage",
+ compiler_version: "0.4.23",
+ optimization: false,
+ contract_source_code:
+ "pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }",
+ abi: [
+ %{
+ "constant" => false,
+ "inputs" => [%{"name" => "x", "type" => "uint256"}],
+ "name" => "set",
+ "outputs" => [],
+ "payable" => false,
+ "stateMutability" => "nonpayable",
+ "type" => "function"
+ },
+ %{
+ "constant" => true,
+ "inputs" => [],
+ "name" => "get",
+ "outputs" => [%{"name" => "", "type" => "uint256"}],
+ "payable" => false,
+ "stateMutability" => "view",
+ "type" => "function"
+ }
+ ]
+ }
+
+ {:ok, valid_attrs: valid_attrs, address: created_contract_address}
+ end
+
+ test "with valid data creates a smart contract", %{valid_attrs: valid_attrs} do
+ assert {:ok, %SmartContract{} = smart_contract} = SmartContract.create_smart_contract(valid_attrs)
+ assert smart_contract.name == "SimpleStorage"
+ assert smart_contract.compiler_version == "0.4.23"
+ assert smart_contract.optimization == false
+ assert smart_contract.contract_source_code != ""
+ assert smart_contract.abi != ""
+
+ assert Repo.get_by(
+ Address.Name,
+ address_hash: smart_contract.address_hash,
+ name: smart_contract.name,
+ primary: true
+ )
+ end
+
+ test "clears an existing primary name and sets the new one", %{valid_attrs: valid_attrs, address: address} do
+ insert(:address_name, address: address, primary: true)
+ assert {:ok, %SmartContract{} = smart_contract} = SmartContract.create_smart_contract(valid_attrs)
+
+ assert Repo.get_by(
+ Address.Name,
+ address_hash: smart_contract.address_hash,
+ name: smart_contract.name,
+ primary: true
+ )
+ end
+
+ test "trims whitespace from address name", %{valid_attrs: valid_attrs} do
+ attrs = %{valid_attrs | name: " SimpleStorage "}
+ assert {:ok, _} = SmartContract.create_smart_contract(attrs)
+ assert Repo.get_by(Address.Name, name: "SimpleStorage")
+ end
+
+ test "sets the address verified field to true", %{valid_attrs: valid_attrs} do
+ assert {:ok, %SmartContract{} = smart_contract} = SmartContract.create_smart_contract(valid_attrs)
+
+ assert Repo.get_by(Address, hash: smart_contract.address_hash).verified == true
+ end
+ end
+
+ describe "update_smart_contract/1" do
+ setup do
+ smart_contract_bytecode =
+ "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029"
+
+ created_contract_address =
+ insert(
+ :address,
+ hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
+ contract_code: smart_contract_bytecode
+ )
+
+ transaction =
+ :transaction
+ |> insert()
+ |> with_block()
+
+ insert(
+ :internal_transaction_create,
+ transaction: transaction,
+ index: 0,
+ created_contract_address: created_contract_address,
+ created_contract_code: smart_contract_bytecode,
+ block_number: transaction.block_number,
+ block_hash: transaction.block_hash,
+ block_index: 0,
+ transaction_index: transaction.index
+ )
+
+ valid_attrs = %{
+ address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
+ name: "SimpleStorage",
+ compiler_version: "0.4.23",
+ optimization: false,
+ contract_source_code:
+ "pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }",
+ abi: [
+ %{
+ "constant" => false,
+ "inputs" => [%{"name" => "x", "type" => "uint256"}],
+ "name" => "set",
+ "outputs" => [],
+ "payable" => false,
+ "stateMutability" => "nonpayable",
+ "type" => "function"
+ },
+ %{
+ "constant" => true,
+ "inputs" => [],
+ "name" => "get",
+ "outputs" => [%{"name" => "", "type" => "uint256"}],
+ "payable" => false,
+ "stateMutability" => "view",
+ "type" => "function"
+ }
+ ],
+ partially_verified: true
+ }
+
+ secondary_sources = [
+ %{
+ file_name: "storage.sol",
+ contract_source_code:
+ "pragma solidity >=0.7.0 <0.9.0;contract Storage {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
+ address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
+ },
+ %{
+ file_name: "storage_1.sol",
+ contract_source_code:
+ "pragma solidity >=0.7.0 <0.9.0;contract Storage_1 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
+ address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
+ }
+ ]
+
+ changed_sources = [
+ %{
+ file_name: "storage_2.sol",
+ contract_source_code:
+ "pragma solidity >=0.7.0 <0.9.0;contract Storage_2 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
+ address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
+ },
+ %{
+ file_name: "storage_3.sol",
+ contract_source_code:
+ "pragma solidity >=0.7.0 <0.9.0;contract Storage_3 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
+ address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
+ }
+ ]
+
+ _ = SmartContract.create_smart_contract(valid_attrs, [], secondary_sources)
+
+ {:ok,
+ valid_attrs: valid_attrs,
+ address: created_contract_address,
+ secondary_sources: secondary_sources,
+ changed_sources: changed_sources}
+ end
+
+ test "change partially_verified field", %{valid_attrs: valid_attrs, address: address} do
+ sc_before_call = Repo.get_by(SmartContract, address_hash: address.hash)
+ assert sc_before_call.name == Map.get(valid_attrs, :name)
+ assert sc_before_call.partially_verified == Map.get(valid_attrs, :partially_verified)
+
+ assert {:ok, %SmartContract{}} =
+ SmartContract.update_smart_contract(%{
+ address_hash: address.hash,
+ partially_verified: false,
+ contract_source_code: "new code"
+ })
+
+ sc_after_call = Repo.get_by(SmartContract, address_hash: address.hash)
+ assert sc_after_call.name == Map.get(valid_attrs, :name)
+ assert sc_after_call.partially_verified == false
+ assert sc_after_call.compiler_version == Map.get(valid_attrs, :compiler_version)
+ assert sc_after_call.optimization == Map.get(valid_attrs, :optimization)
+ assert sc_after_call.contract_source_code == "new code"
+ end
+
+ test "check nothing changed", %{valid_attrs: valid_attrs, address: address} do
+ sc_before_call = Repo.get_by(SmartContract, address_hash: address.hash)
+ assert sc_before_call.name == Map.get(valid_attrs, :name)
+ assert sc_before_call.partially_verified == Map.get(valid_attrs, :partially_verified)
+
+ assert {:ok, %SmartContract{}} = SmartContract.update_smart_contract(%{address_hash: address.hash})
+
+ sc_after_call = Repo.get_by(SmartContract, address_hash: address.hash)
+ assert sc_after_call.name == Map.get(valid_attrs, :name)
+ assert sc_after_call.partially_verified == Map.get(valid_attrs, :partially_verified)
+ assert sc_after_call.compiler_version == Map.get(valid_attrs, :compiler_version)
+ assert sc_after_call.optimization == Map.get(valid_attrs, :optimization)
+ assert sc_after_call.contract_source_code == Map.get(valid_attrs, :contract_source_code)
+ end
+
+ test "check additional sources update", %{
+ address: address,
+ secondary_sources: secondary_sources,
+ changed_sources: changed_sources
+ } do
+ sc_before_call = Repo.get_by(Address, hash: address.hash) |> Repo.preload(:smart_contract_additional_sources)
+
+ assert sc_before_call.smart_contract_additional_sources
+ |> Enum.with_index()
+ |> Enum.all?(fn {el, ind} ->
+ {:ok, src} = Enum.fetch(secondary_sources, ind)
+
+ el.file_name == Map.get(src, :file_name) and
+ el.contract_source_code == Map.get(src, :contract_source_code)
+ end)
+
+ assert {:ok, %SmartContract{}} =
+ SmartContract.update_smart_contract(%{address_hash: address.hash}, [], changed_sources)
+
+ sc_after_call = Repo.get_by(Address, hash: address.hash) |> Repo.preload(:smart_contract_additional_sources)
+
+ assert sc_after_call.smart_contract_additional_sources
+ |> Enum.with_index()
+ |> Enum.all?(fn {el, ind} ->
+ {:ok, src} = Enum.fetch(changed_sources, ind)
+
+ el.file_name == Map.get(src, :file_name) and
+ el.contract_source_code == Map.get(src, :contract_source_code)
+ end)
+ end
+ end
+
+ test "get_smart_contract_abi/1 returns empty [] abi if implementation address is null" do
+ assert SmartContract.get_smart_contract_abi(nil) == []
+ end
+
+ test "get_smart_contract_abi/1 returns [] if implementation is not verified" do
+ implementation_contract_address = insert(:contract_address)
+
+ implementation_contract_address_hash_string =
+ Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
+
+ assert SmartContract.get_smart_contract_abi("0x" <> implementation_contract_address_hash_string) == []
+ end
+
+ @abi [
+ %{
+ "constant" => false,
+ "inputs" => [%{"name" => "x", "type" => "uint256"}],
+ "name" => "set",
+ "outputs" => [],
+ "payable" => false,
+ "stateMutability" => "nonpayable",
+ "type" => "function"
+ },
+ %{
+ "constant" => true,
+ "inputs" => [],
+ "name" => "get",
+ "outputs" => [%{"name" => "", "type" => "uint256"}],
+ "payable" => false,
+ "stateMutability" => "view",
+ "type" => "function"
+ }
+ ]
+
+ @proxy_abi [
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [%{"type" => "bool", "name" => ""}],
+ "name" => "upgradeTo",
+ "inputs" => [%{"type" => "address", "name" => "newImplementation"}],
+ "constant" => false
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "uint256", "name" => ""}],
+ "name" => "version",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "address", "name" => ""}],
+ "name" => "implementation",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [],
+ "name" => "renounceOwnership",
+ "inputs" => [],
+ "constant" => false
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "address", "name" => ""}],
+ "name" => "getOwner",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "address", "name" => ""}],
+ "name" => "getProxyStorage",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [],
+ "name" => "transferOwnership",
+ "inputs" => [%{"type" => "address", "name" => "_newOwner"}],
+ "constant" => false
+ },
+ %{
+ "type" => "constructor",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "inputs" => [
+ %{"type" => "address", "name" => "_proxyStorage"},
+ %{"type" => "address", "name" => "_implementationAddress"}
+ ]
+ },
+ %{"type" => "fallback", "stateMutability" => "nonpayable", "payable" => false},
+ %{
+ "type" => "event",
+ "name" => "Upgraded",
+ "inputs" => [
+ %{"type" => "uint256", "name" => "version", "indexed" => false},
+ %{"type" => "address", "name" => "implementation", "indexed" => true}
+ ],
+ "anonymous" => false
+ },
+ %{
+ "type" => "event",
+ "name" => "OwnershipRenounced",
+ "inputs" => [%{"type" => "address", "name" => "previousOwner", "indexed" => true}],
+ "anonymous" => false
+ },
+ %{
+ "type" => "event",
+ "name" => "OwnershipTransferred",
+ "inputs" => [
+ %{"type" => "address", "name" => "previousOwner", "indexed" => true},
+ %{"type" => "address", "name" => "newOwner", "indexed" => true}
+ ],
+ "anonymous" => false
+ }
+ ]
+
+ test "get_smart_contract_abi/1 returns implementation abi if implementation is verified" do
+ proxy_contract_address = insert(:contract_address)
+ insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
+
+ implementation_contract_address = insert(:contract_address)
+
+ insert(:smart_contract,
+ address_hash: implementation_contract_address.hash,
+ abi: @abi,
+ contract_code_md5: "123"
+ )
+
+ implementation_contract_address_hash_string =
+ Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
+
+ implementation_abi = SmartContract.get_smart_contract_abi("0x" <> implementation_contract_address_hash_string)
+
+ assert implementation_abi == @abi
+ end
+
+ defp expect_address_in_response(string_implementation_address_hash) do
+ mock_empty_logic_storage_pointer_request()
+ |> mock_empty_beacon_storage_pointer_request()
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, string_implementation_address_hash}
+ end)
+ end
+
+ defp mock_empty_logic_storage_pointer_request do
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
+ end
+
+ defp mock_empty_beacon_storage_pointer_request(mox) do
+ expect(mox, :json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
+ end
+
+ defp mock_empty_eip_1822_storage_pointer_request(mox) do
+ expect(mox, :json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
+ end
+
+ defp mock_empty_oz_storage_pointer_request(mox) do
+ expect(mox, :json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
+ end
end
diff --git a/apps/explorer/test/explorer/chain/supply/rsk_test.exs b/apps/explorer/test/explorer/chain/supply/rsk_test.exs
index a2e59294d221..5592953d5f3e 100644
--- a/apps/explorer/test/explorer/chain/supply/rsk_test.exs
+++ b/apps/explorer/test/explorer/chain/supply/rsk_test.exs
@@ -9,6 +9,8 @@ defmodule Explorer.Chain.Supply.RSKTest do
@coin_address "0x0000000000000000000000000000000001000006"
@mult 1_000_000_000_000_000_000
+ setup :verify_on_exit!
+
test "total is 21_000_000" do
assert Decimal.equal?(RSK.total(), Decimal.new(21_000_000))
end
diff --git a/apps/explorer/test/explorer/chain/token/instance_test.exs b/apps/explorer/test/explorer/chain/token/instance_test.exs
new file mode 100644
index 000000000000..96f78e79fcc6
--- /dev/null
+++ b/apps/explorer/test/explorer/chain/token/instance_test.exs
@@ -0,0 +1,83 @@
+defmodule Explorer.Chain.Token.InstanceTest do
+ use Explorer.DataCase
+
+ alias Explorer.Repo
+ alias Explorer.Chain.Token.Instance
+
+ describe "stream_not_inserted_token_instances/2" do
+ test "reduces with given reducer and accumulator for ERC-721 token" do
+ token_contract_address = insert(:contract_address)
+ token = insert(:token, contract_address: token_contract_address, type: "ERC-721")
+
+ transaction =
+ :transaction
+ |> insert()
+ |> with_block(insert(:block, number: 1))
+
+ token_transfer =
+ insert(
+ :token_transfer,
+ block_number: 1000,
+ to_address: build(:address),
+ transaction: transaction,
+ token_contract_address: token_contract_address,
+ token: token,
+ token_ids: [11]
+ )
+
+ assert [result] = 5 |> Instance.not_inserted_token_instances_query() |> Repo.all()
+ assert result.token_id == List.first(token_transfer.token_ids)
+ assert result.contract_address_hash == token_transfer.token_contract_address_hash
+ end
+
+ test "does not fetch token transfers without token_ids" do
+ token_contract_address = insert(:contract_address)
+ token = insert(:token, contract_address: token_contract_address, type: "ERC-721")
+
+ transaction =
+ :transaction
+ |> insert()
+ |> with_block(insert(:block, number: 1))
+
+ insert(
+ :token_transfer,
+ block_number: 1000,
+ to_address: build(:address),
+ transaction: transaction,
+ token_contract_address: token_contract_address,
+ token: token,
+ token_ids: nil
+ )
+
+ assert [] = 5 |> Instance.not_inserted_token_instances_query() |> Repo.all()
+ end
+
+ test "do not fetch records with token instances" do
+ token_contract_address = insert(:contract_address)
+ token = insert(:token, contract_address: token_contract_address, type: "ERC-721")
+
+ transaction =
+ :transaction
+ |> insert()
+ |> with_block(insert(:block, number: 1))
+
+ token_transfer =
+ insert(
+ :token_transfer,
+ block_number: 1000,
+ to_address: build(:address),
+ transaction: transaction,
+ token_contract_address: token_contract_address,
+ token: token,
+ token_ids: [11]
+ )
+
+ insert(:token_instance,
+ token_id: List.first(token_transfer.token_ids),
+ token_contract_address_hash: token_transfer.token_contract_address_hash
+ )
+
+ assert [] = 5 |> Instance.not_inserted_token_instances_query() |> Repo.all()
+ end
+ end
+end
diff --git a/apps/explorer/test/explorer/chain/token_transfer_test.exs b/apps/explorer/test/explorer/chain/token_transfer_test.exs
index da4720281a70..3139f318cb39 100644
--- a/apps/explorer/test/explorer/chain/token_transfer_test.exs
+++ b/apps/explorer/test/explorer/chain/token_transfer_test.exs
@@ -3,7 +3,7 @@ defmodule Explorer.Chain.TokenTransferTest do
import Explorer.Factory
- alias Explorer.{PagingOptions, Repo}
+ alias Explorer.PagingOptions
alias Explorer.Chain.TokenTransfer
doctest Explorer.Chain.TokenTransfer
@@ -325,4 +325,28 @@ defmodule Explorer.Chain.TokenTransferTest do
assert Enum.member?(page_two, transaction_one_bytes) == true
end
end
+
+ describe "uncataloged_token_transfer_block_numbers/0" do
+ test "returns a list of block numbers" do
+ block = insert(:block)
+ address = insert(:address)
+
+ log =
+ insert(:token_transfer_log,
+ transaction:
+ insert(:transaction,
+ block_number: block.number,
+ block_hash: block.hash,
+ cumulative_gas_used: 0,
+ gas_used: 0,
+ index: 0
+ ),
+ block: block,
+ address_hash: address.hash
+ )
+
+ block_number = log.block_number
+ assert {:ok, [^block_number]} = TokenTransfer.uncataloged_token_transfer_block_numbers()
+ end
+ end
end
diff --git a/apps/explorer/test/explorer/chain/transaction_test.exs b/apps/explorer/test/explorer/chain/transaction_test.exs
index 54fdead2cd40..7a5ce5bbe51a 100644
--- a/apps/explorer/test/explorer/chain/transaction_test.exs
+++ b/apps/explorer/test/explorer/chain/transaction_test.exs
@@ -4,10 +4,15 @@ defmodule Explorer.Chain.TransactionTest do
import Mox
alias Ecto.Changeset
- alias Explorer.Chain.Transaction
+ alias Explorer.Chain.{Address, InternalTransaction, Transaction}
+ alias Explorer.PagingOptions
doctest Transaction
+ setup :set_mox_global
+
+ setup :verify_on_exit!
+
describe "changeset/2" do
test "with valid attributes" do
assert %Changeset{valid?: true} =
@@ -247,7 +252,7 @@ defmodule Explorer.Chain.TransactionTest do
test "that a transaction that is not a contract call returns a commensurate error" do
transaction = insert(:transaction)
- assert Transaction.decoded_input_data(transaction, []) == {:error, :not_a_contract_call}
+ assert {{:error, :not_a_contract_call}, _, _} = Transaction.decoded_input_data(transaction, [])
end
test "that a contract call transaction that has no verified contract returns a commensurate error" do
@@ -256,7 +261,7 @@ defmodule Explorer.Chain.TransactionTest do
|> insert(to_address: insert(:contract_address))
|> Repo.preload(to_address: :smart_contract)
- assert Transaction.decoded_input_data(transaction, []) == {:error, :contract_not_verified, []}
+ assert {{:error, :contract_not_verified, []}, _, _} = Transaction.decoded_input_data(transaction, [])
end
test "that a contract call transaction that has a verified contract returns the decoded input data" do
@@ -265,10 +270,10 @@ defmodule Explorer.Chain.TransactionTest do
|> insert()
|> Repo.preload(to_address: :smart_contract)
- get_eip1967_implementation()
+ request_zero_implementations()
- assert Transaction.decoded_input_data(transaction, []) ==
- {:ok, "60fe47b1", "set(uint256 x)", [{"x", "uint256", 50}]}
+ assert {{:ok, "60fe47b1", "set(uint256 x)", [{"x", "uint256", 50}]}, _, _} =
+ Transaction.decoded_input_data(transaction, [])
end
test "that a contract call will look up a match in contract_methods table" do
@@ -288,10 +293,10 @@ defmodule Explorer.Chain.TransactionTest do
|> insert(to_address: contract.address, input: "0x" <> input_data)
|> Repo.preload(to_address: :smart_contract)
- get_eip1967_implementation()
+ request_zero_implementations()
- assert Transaction.decoded_input_data(transaction, []) ==
- {:ok, "60fe47b1", "set(uint256 x)", [{"x", "uint256", 10}]}
+ assert {{:ok, "60fe47b1", "set(uint256 x)", [{"x", "uint256", 10}]}, _, _} =
+ Transaction.decoded_input_data(transaction, [])
end
end
@@ -309,7 +314,482 @@ defmodule Explorer.Chain.TransactionTest do
end
end
- def get_eip1967_implementation do
+ describe "address_to_transactions_tasks_range_of_blocks/2" do
+ test "returns empty extremums if no transactions" do
+ address = insert(:address)
+
+ extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, [])
+
+ assert extremums == %{
+ :min_block_number => nil,
+ :max_block_number => 0
+ }
+ end
+
+ test "returns correct extremums for from_address" do
+ address = insert(:address)
+
+ :transaction
+ |> insert(from_address: address)
+ |> with_block(insert(:block, number: 1000))
+
+ extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, [])
+
+ assert extremums == %{
+ :min_block_number => 1000,
+ :max_block_number => 1000
+ }
+ end
+
+ test "returns correct extremums for to_address" do
+ address = insert(:address)
+
+ :transaction
+ |> insert(to_address: address)
+ |> with_block(insert(:block, number: 1000))
+
+ extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, [])
+
+ assert extremums == %{
+ :min_block_number => 1000,
+ :max_block_number => 1000
+ }
+ end
+
+ test "returns correct extremums for created_contract_address" do
+ address = insert(:address)
+
+ :transaction
+ |> insert(created_contract_address: address)
+ |> with_block(insert(:block, number: 1000))
+
+ extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, [])
+
+ assert extremums == %{
+ :min_block_number => 1000,
+ :max_block_number => 1000
+ }
+ end
+
+ test "returns correct extremums for multiple number of transactions" do
+ address = insert(:address)
+
+ :transaction
+ |> insert(created_contract_address: address)
+ |> with_block(insert(:block, number: 1000))
+
+ :transaction
+ |> insert(created_contract_address: address)
+ |> with_block(insert(:block, number: 999))
+
+ :transaction
+ |> insert(created_contract_address: address)
+ |> with_block(insert(:block, number: 1003))
+
+ :transaction
+ |> insert(from_address: address)
+ |> with_block(insert(:block, number: 1001))
+
+ :transaction
+ |> insert(from_address: address)
+ |> with_block(insert(:block, number: 1004))
+
+ :transaction
+ |> insert(to_address: address)
+ |> with_block(insert(:block, number: 1002))
+
+ :transaction
+ |> insert(to_address: address)
+ |> with_block(insert(:block, number: 998))
+
+ extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, [])
+
+ assert extremums == %{
+ :min_block_number => 998,
+ :max_block_number => 1004
+ }
+ end
+ end
+
+ describe "address_to_transactions_with_rewards/2" do
+ test "without transactions" do
+ %Address{hash: address_hash} = insert(:address)
+
+ assert Repo.aggregate(Transaction, :count, :hash) == 0
+
+ assert [] == Transaction.address_to_transactions_with_rewards(address_hash)
+ end
+
+ test "with from transactions" do
+ %Address{hash: address_hash} = address = insert(:address)
+
+ transaction =
+ :transaction
+ |> insert(from_address: address)
+ |> with_block()
+
+ assert [transaction] ==
+ Transaction.address_to_transactions_with_rewards(address_hash, direction: :from)
+ |> Repo.preload([:block, :to_address, :from_address])
+ end
+
+ test "with to transactions" do
+ %Address{hash: address_hash} = address = insert(:address)
+
+ transaction =
+ :transaction
+ |> insert(to_address: address)
+ |> with_block()
+
+ assert [transaction] ==
+ Transaction.address_to_transactions_with_rewards(address_hash, direction: :to)
+ |> Repo.preload([:block, :to_address, :from_address])
+ end
+
+ test "with to and from transactions and direction: :from" do
+ %Address{hash: address_hash} = address = insert(:address)
+
+ transaction =
+ :transaction
+ |> insert(from_address: address)
+ |> with_block()
+
+ # only contains "from" transaction
+ assert [transaction] ==
+ Transaction.address_to_transactions_with_rewards(address_hash, direction: :from)
+ |> Repo.preload([:block, :to_address, :from_address])
+ end
+
+ test "with to and from transactions and direction: :to" do
+ %Address{hash: address_hash} = address = insert(:address)
+
+ transaction =
+ :transaction
+ |> insert(to_address: address)
+ |> with_block()
+
+ assert [transaction] ==
+ Transaction.address_to_transactions_with_rewards(address_hash, direction: :to)
+ |> Repo.preload([:block, :to_address, :from_address])
+ end
+
+ test "with to and from transactions and no :direction option" do
+ %Address{hash: address_hash} = address = insert(:address)
+ block = insert(:block)
+
+ transaction1 =
+ :transaction
+ |> insert(to_address: address)
+ |> with_block(block)
+
+ transaction2 =
+ :transaction
+ |> insert(from_address: address)
+ |> with_block(block)
+
+ assert [transaction2, transaction1] ==
+ Transaction.address_to_transactions_with_rewards(address_hash)
+ |> Repo.preload([:block, :to_address, :from_address])
+ end
+
+ test "does not include non-contract-creation parent transactions" do
+ transaction =
+ %Transaction{} =
+ :transaction
+ |> insert()
+ |> with_block()
+
+ %InternalTransaction{created_contract_address: address} =
+ insert(:internal_transaction_create,
+ transaction: transaction,
+ index: 0,
+ block_number: transaction.block_number,
+ block_hash: transaction.block_hash,
+ block_index: 0,
+ transaction_index: transaction.index
+ )
+
+ assert [] == Transaction.address_to_transactions_with_rewards(address.hash)
+ end
+
+ test "returns transactions that have token transfers for the given to_address" do
+ %Address{hash: address_hash} = address = insert(:address)
+
+ transaction =
+ :transaction
+ |> insert(to_address: address, to_address_hash: address.hash)
+ |> with_block()
+
+ insert(
+ :token_transfer,
+ to_address: address,
+ transaction: transaction
+ )
+
+ assert [transaction.hash] ==
+ Transaction.address_to_transactions_with_rewards(address_hash)
+ |> Enum.map(& &1.hash)
+ end
+
+ test "with transactions can be paginated" do
+ %Address{hash: address_hash} = address = insert(:address)
+
+ second_page_hashes =
+ 2
+ |> insert_list(:transaction, from_address: address)
+ |> with_block()
+ |> Enum.map(& &1.hash)
+
+ %Transaction{block_number: block_number, index: index} =
+ :transaction
+ |> insert(from_address: address)
+ |> with_block()
+
+ assert second_page_hashes ==
+ address_hash
+ |> Transaction.address_to_transactions_with_rewards(
+ paging_options: %PagingOptions{
+ key: {block_number, index},
+ page_size: 2
+ }
+ )
+ |> Enum.map(& &1.hash)
+ |> Enum.reverse()
+ end
+
+ test "returns results in reverse chronological order by block number and transaction index" do
+ %Address{hash: address_hash} = address = insert(:address)
+
+ a_block = insert(:block, number: 6000)
+
+ %Transaction{hash: first} =
+ :transaction
+ |> insert(to_address: address)
+ |> with_block(a_block)
+
+ %Transaction{hash: second} =
+ :transaction
+ |> insert(to_address: address)
+ |> with_block(a_block)
+
+ %Transaction{hash: third} =
+ :transaction
+ |> insert(to_address: address)
+ |> with_block(a_block)
+
+ %Transaction{hash: fourth} =
+ :transaction
+ |> insert(to_address: address)
+ |> with_block(a_block)
+
+ b_block = insert(:block, number: 2000)
+
+ %Transaction{hash: fifth} =
+ :transaction
+ |> insert(to_address: address)
+ |> with_block(b_block)
+
+ %Transaction{hash: sixth} =
+ :transaction
+ |> insert(to_address: address)
+ |> with_block(b_block)
+
+ result =
+ address_hash
+ |> Transaction.address_to_transactions_with_rewards()
+ |> Enum.map(& &1.hash)
+
+ assert [fourth, third, second, first, sixth, fifth] == result
+ end
+
+ test "with emission rewards" do
+ Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true)
+
+ Application.put_env(:explorer, Explorer.Chain.Block.Reward,
+ validators_contract_address: "0x0000000000000000000000000000000000000005",
+ keys_manager_contract_address: "0x0000000000000000000000000000000000000006"
+ )
+
+ consumer_pid = start_supervised!(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand)
+ :erlang.trace(consumer_pid, true, [:receive])
+
+ block = insert(:block)
+
+ block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower)
+ block_miner_hash = block.miner_hash
+
+ insert(
+ :reward,
+ address_hash: block.miner_hash,
+ block_hash: block.hash,
+ address_type: :validator
+ )
+
+ insert(
+ :reward,
+ address_hash: block.miner_hash,
+ block_hash: block.hash,
+ address_type: :emission_funds
+ )
+
+ # isValidator => true
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
+ {:ok,
+ [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]}
+ end
+ )
+
+ # getPayoutByMining => 0x0000000000000000000000000000000000000001
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
+ {:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]}
+ end
+ )
+
+ res = Transaction.address_to_transactions_with_rewards(block.miner.hash)
+ assert [{_, _}] = res
+
+ assert_receive {:trace, ^consumer_pid, :receive, {:"$gen_cast", {:fetch_or_update, ^block_miner_hash}}}, 1000
+ :timer.sleep(500)
+
+ on_exit(fn ->
+ Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false)
+
+ Application.put_env(:explorer, Explorer.Chain.Block.Reward,
+ validators_contract_address: nil,
+ keys_manager_contract_address: nil
+ )
+ end)
+ end
+
+ test "with emission rewards and transactions" do
+ Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true)
+
+ Application.put_env(:explorer, Explorer.Chain.Block.Reward,
+ validators_contract_address: "0x0000000000000000000000000000000000000005",
+ keys_manager_contract_address: "0x0000000000000000000000000000000000000006"
+ )
+
+ consumer_pid = start_supervised!(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand)
+ :erlang.trace(consumer_pid, true, [:receive])
+
+ block = insert(:block)
+
+ block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower)
+ block_miner_hash = block.miner_hash
+
+ insert(
+ :reward,
+ address_hash: block.miner_hash,
+ block_hash: block.hash,
+ address_type: :validator
+ )
+
+ insert(
+ :reward,
+ address_hash: block.miner_hash,
+ block_hash: block.hash,
+ address_type: :emission_funds
+ )
+
+ :transaction
+ |> insert(to_address: block.miner)
+ |> with_block(block)
+ |> Repo.preload(:token_transfers)
+
+ # isValidator => true
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
+ {:ok,
+ [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]}
+ end
+ )
+
+ # getPayoutByMining => 0x0000000000000000000000000000000000000001
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
+ {:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]}
+ end
+ )
+
+ assert [_, {_, _}] = Transaction.address_to_transactions_with_rewards(block.miner.hash, direction: :to)
+
+ assert_receive {:trace, ^consumer_pid, :receive, {:"$gen_cast", {:fetch_or_update, ^block_miner_hash}}}, 1000
+ :timer.sleep(500)
+
+ on_exit(fn ->
+ Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false)
+
+ Application.put_env(:explorer, Explorer.Chain.Block.Reward,
+ validators_contract_address: nil,
+ keys_manager_contract_address: nil
+ )
+ end)
+ end
+
+ test "with transactions if rewards are not in the range of blocks" do
+ Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true)
+
+ block = insert(:block)
+
+ insert(
+ :reward,
+ address_hash: block.miner_hash,
+ block_hash: block.hash,
+ address_type: :validator
+ )
+
+ insert(
+ :reward,
+ address_hash: block.miner_hash,
+ block_hash: block.hash,
+ address_type: :emission_funds
+ )
+
+ :transaction
+ |> insert(from_address: block.miner)
+ |> with_block()
+ |> Repo.preload(:token_transfers)
+
+ assert [_] = Transaction.address_to_transactions_with_rewards(block.miner.hash, direction: :from)
+
+ Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false)
+ end
+
+ test "with emissions rewards, but feature disabled" do
+ Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false)
+
+ block = insert(:block)
+
+ insert(
+ :reward,
+ address_hash: block.miner_hash,
+ block_hash: block.hash,
+ address_type: :validator
+ )
+
+ insert(
+ :reward,
+ address_hash: block.miner_hash,
+ block_hash: block.hash,
+ address_type: :emission_funds
+ )
+
+ assert [] == Transaction.address_to_transactions_with_rewards(block.miner.hash)
+ end
+ end
+
+ # EIP-1967 + EIP-1822
+ defp request_zero_implementations do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
@@ -347,5 +827,17 @@ defmodule Explorer.Chain.TransactionTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
+ |> expect(:json_rpc, fn %{
+ id: 0,
+ method: "eth_getStorageAt",
+ params: [
+ _,
+ "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
+ "latest"
+ ]
+ },
+ _options ->
+ {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
+ end)
end
end
diff --git a/apps/explorer/test/explorer/chain/withdrawal_test.exs b/apps/explorer/test/explorer/chain/withdrawal_test.exs
index ebf88f3499aa..ab797f617219 100644
--- a/apps/explorer/test/explorer/chain/withdrawal_test.exs
+++ b/apps/explorer/test/explorer/chain/withdrawal_test.exs
@@ -3,7 +3,6 @@ defmodule Explorer.Chain.WithdrawalTest do
alias Ecto.Changeset
alias Explorer.Chain.Withdrawal
- alias Explorer.Chain
describe "changeset/2" do
test "with valid attributes" do
diff --git a/apps/explorer/test/explorer/chain_spec/geth/importer_test.exs b/apps/explorer/test/explorer/chain_spec/geth/importer_test.exs
index 36eebf24a71d..e8aa80eac1ea 100644
--- a/apps/explorer/test/explorer/chain_spec/geth/importer_test.exs
+++ b/apps/explorer/test/explorer/chain_spec/geth/importer_test.exs
@@ -11,13 +11,23 @@ defmodule Explorer.ChainSpec.Geth.ImporterTest do
setup :set_mox_global
- @genesis "#{File.cwd!()}/test/support/fixture/chain_spec/qdai_genesis.json"
- |> File.read!()
- |> Jason.decode!()
+ setup :verify_on_exit!
+
+ @geth_genesis "#{File.cwd!()}/test/support/fixture/chain_spec/qdai_genesis.json"
+ |> File.read!()
+ |> Jason.decode!()
+
+ @polygon_genesis "#{File.cwd!()}/test/support/fixture/chain_spec/polygon_genesis.json"
+ |> File.read!()
+ |> Jason.decode!()
+
+ @optimism_genesis "#{File.cwd!()}/test/support/fixture/chain_spec/optimism_genesis.json"
+ |> File.read!()
+ |> Jason.decode!()
describe "genesis_accounts/1" do
test "parses coin balance and contract code" do
- coin_balances = Importer.genesis_accounts(@genesis)
+ coin_balances = Importer.genesis_accounts(@geth_genesis)
assert Enum.count(coin_balances) == 3
@@ -32,6 +42,40 @@ defmodule Explorer.ChainSpec.Geth.ImporterTest do
} ==
List.first(coin_balances)
end
+
+ test "parses polygon coin balance and contract code" do
+ coin_balances = Importer.genesis_accounts(@polygon_genesis)
+
+ assert Enum.count(coin_balances) == 2
+
+ assert %{
+ address_hash: %Explorer.Chain.Hash{
+ byte_count: 20,
+ bytes: <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1>>
+ },
+ value: 0,
+ contract_code:
+ "0x608060405234801561001057600080fd5b50600436106101b05760003560e01c806370a08231116100ef578063ce513b6f11610092578063ce513b6f14610398578063dd62ed3e146103ab578063e0563ab1146103be578063ea0fee4f146103c7578063eacdc5ff146103cf578063eeb49945146103d8578063f3f43703146103eb578063fd242c14146103fe57600080fd5b806370a08231146102e7578063947287cf146102fa57806395d89b411461030357806397e5230d1461030b578063981b24d014610315578063a457c2d714610328578063a9059cbb1461033b578063c6b61e4c1461034e57600080fd5b8063395093511161015757806339509351146102735780633b878c22146102865780633ccfd60b1461028f5780633fd50001146102975780634ee2cd7e146102aa57806351351d53146102bd57806361cc2763146102cb57806362656003146102de57600080fd5b806306fdde03146101b5578063095ea7b3146101d35780630f50287c146101f657806318160ddd1461020b57806323b872dd1461021d578063284017f5146102305780632e17de7814610251578063313ce56714610264575b600080fd5b6101bd610411565b6040516101ca91906119ba565b60405180910390f35b6101e66101e13660046119e2565b6104a3565b60405190151581526020016101ca565b610209610204366004611a0e565b6104bd565b005b6035545b6040519081526020016101ca565b6101e661022b366004611a46565b61074f565b61023961202081565b6040516001600160a01b0390911681526020016101ca565b61020961025f366004611a87565b610773565b604051601281526020016101ca565b6101e66102813660046119e2565b61078a565b61023961101081565b6102096107ac565b61020f6102a5366004611a87565b6108bd565b61020f6102b83660046119e2565b6108de565b6102396002600160a01b0381565b6102096102d9366004611b10565b6108f1565b61020f60cc5481565b61020f6102f5366004611c29565b610b1f565b61020f61520881565b6101bd610b3a565b61020f620249f081565b61020f610323366004611a87565b610b49565b6101e66103363660046119e2565b610b54565b6101e66103493660046119e2565b610bcf565b61037d61035c366004611a87565b60ce6020526000908152604090208054600182015460029092015490919083565b604080519384526020840192909252908201526060016101ca565b61020f6103a6366004611c29565b610bdd565b61020f6103b9366004611c46565b610c0b565b61023961203081565b61020f600181565b61020f60cd5481565b6102096103e6366004611c7f565b610c36565b61020f6103f9366004611c29565b610d08565b61020f61040c366004611a87565b610d2f565b60606036805461042090611d08565b80601f016020809104026020016040519081016040528092919081815260200182805461044c90611d08565b80156104995780601f1061046e57610100808354040283529160200191610499565b820191906000526020600020905b81548152906001019060200180831161047c57829003601f168201915b5050505050905090565b6000336104b1818585610d79565b60019150505b92915050565b336002600160a01b03146105065760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b60448201526064015b60405180910390fd5b60cd80546000918261051783611d58565b9190505590508083146105625760405162461bcd60e51b815260206004820152601360248201527215539156141150d5115117d15413d0d217d251606a1b60448201526064016104fd565b81356020830135116105ac5760405162461bcd60e51b81526020600482015260136024820152721393d7d09313d0d2d4d7d0d3d3535255151151606a1b60448201526064016104fd565b60cc546105be83356020850135611d71565b6105c9906001611d84565b6105d39190611dad565b1561062e5760405162461bcd60e51b815260206004820152602560248201527f45504f43485f4d5553545f42455f444956495349424c455f42595f45504f43486044820152645f53495a4560d81b60648201526084016104fd565b813560ce600061063f600185611d71565b815260200190815260200160002060010154600161065d9190611d84565b146106a05760405162461bcd60e51b8152602060048201526013602482015272494e56414c49445f53544152545f424c4f434b60681b60448201526064016104fd565b600081815260ce6020526040902082906106d182828135815560208201356001820155604082013560028201555050565b505060cf80546001810182556000919091526020838101357facb8d954e2cfef495862221e91bd7523613cf8808827cb33edfe4904cc51bf299092018290556040805190850135815284359186917f0ce8712c4dee4bd5a691f0bc1c39594671591e77395f8ebf6a3fb5f63fbea66a910160405180910390a4505050565b60003361075d858285610e9e565b610768858585610f12565b506001949350505050565b61077d33826110b6565b61078733826111e1565b50565b6000336104b181858561079d8383610c0b565b6107a79190611d84565b610d79565b33600090815260d06020526040812060cd5490919081906107ce90849061125a565b808555604051828152919350915033907f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b659060200160405180910390a260c95460cb54604080517f8ca9a95e41b5eece253c93f5b31eed1253aed6b145d8a6e14d913fdf8e7322936020820152338183015260608082018790528251808303909101815260808201928390526316f1983160e01b9092526001600160a01b03938416936316f198319361088693911691608401611dc1565b600060405180830381600087803b1580156108a057600080fd5b505af11580156108b4573d6000803e3d6000fd5b50505050505050565b60cf81815481106108cd57600080fd5b600091825260209091200154905081565b60006108ea83836112cc565b9392505050565b600054610100900460ff16158080156109115750600054600160ff909116105b8061092b5750303b15801561092b575060005460ff166001145b61098e5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016104fd565b6000805460ff1916600117905580156109b1576000805461ff0019166101001790555b6109fb6040518060400160405280600c81526020016b15985b1a59185d1bdc94d95d60a21b815250604051806040016040528060048152602001631594d15560e21b815250611315565b60c980546001600160a01b038089166001600160a01b03199283161790925560ca805488841690831617905560cb80549287169290911691909117905560cc83905560005b8251811015610a9557610a8d838281518110610a5e57610a5e611de5565b602002602001015160000151848381518110610a7c57610a7c611de5565b60200260200101516020015161134a565b600101610a40565b5060cf80546001818101835560009283527facb8d954e2cfef495862221e91bd7523613cf8808827cb33edfe4904cc51bf299091019190915560cd558015610b17576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050565b6001600160a01b031660009081526033602052604090205490565b60606037805461042090611d08565b60006104b782611354565b60003381610b628286610c0b565b905083811015610bc25760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084016104fd565b6107688286868403610d79565b6000336104b1818585610f12565b60cd546001600160a01b038216600090815260d0602052604081209091610c04919061125a565b5092915050565b6001600160a01b03918216600090815260346020908152604080832093909416825291909152205490565b60ca546001600160a01b031633148015610c5d575060cb546001600160a01b038481169116145b610c9a5760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a2a72222a960911b60448201526064016104fd565b7f1bcc0f4c3fad314e585165815f94ecca9b96690a26d6417d7876448a9a867a69610cc9602060008486611dfb565b610cd291611e25565b03610d0257600080610ce78360208187611dfb565b810190610cf491906119e2565b91509150610b17828261134a565b50505050565b60cd546001600160a01b038216600090815260d06020526040812090916104b7919061137f565b600081815260ce60205260408120600101548015610d7057600083815260ce6020526040902054610d609082611d71565b610d6b906001611d84565b6108ea565b60009392505050565b6001600160a01b038316610ddb5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016104fd565b6001600160a01b038216610e3c5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016104fd565b6001600160a01b0383811660008181526034602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b6000610eaa8484610c0b565b90506000198114610d025781811015610f055760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016104fd565b610d028484848403610d79565b6001600160a01b038316610f765760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016104fd565b6001600160a01b038216610fd85760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016104fd565b610fe383838361141d565b6001600160a01b0383166000908152603360205260409020548181101561105b5760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b60648201526084016104fd565b6001600160a01b038085166000818152603360205260408082208686039055928616808252908390208054860190559151600080516020611fd6833981519152906110a99086815260200190565b60405180910390a3610d02565b6001600160a01b0382166111165760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b60648201526084016104fd565b6111228260008361141d565b6001600160a01b038216600090815260336020526040902054818110156111965760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b60648201526084016104fd565b6001600160a01b0383166000818152603360209081526040808320868603905560358054879003905551858152919291600080516020611fd68339815191529101610e91565b505050565b61121381600160cd546111f49190611d84565b6001600160a01b038516600090815260d0602052604090209190611486565b816001600160a01b03167f655c1cd0236fb6dc4916f34c8ff10e3b18fcaea5b344dfc16c36fbb1bdfc5df28260405161124e91815260200190565b60405180910390a25050565b81546000905b83600101548110156112c5576000818152600285016020908152604091829020825180840190935280548352600101549082018190528410156112a357506112c5565b80516112af9084611d84565b92505080806112bd90611d58565b915050611260565b9250929050565b6001600160a01b0382166000908152606560205260408120819081906112f39085906115b1565b915091508161130a5761130585610b1f565b61130c565b805b95945050505050565b600054610100900460ff1661133c5760405162461bcd60e51b81526004016104fd90611e43565b611346828261169f565b5050565b61134682826116df565b60008060006113648460666115b1565b915091508161137557603554611377565b805b949350505050565b60018201546000908082036113985760009150506104b7565b60006113a5600183611d71565b90505b845481106114155760008181526002860160209081526040918290208251808401909352805483526001015490820181905285106113e65750611415565b80516113f29085611d84565b9350816000036114025750611415565b508061140d81611e8e565b9150506113a8565b505092915050565b6001600160a01b038316158061143a57506001600160a01b038216155b61147b5760405162461bcd60e51b81526020600482015260126024820152712a2920a729a322a92fa327a92124a22222a760711b60448201526064016104fd565b6111dc83838361179a565b8160000361149657611496611ea5565b825460018401548181036114ed576040805180820182528581526020808201868152600085815260028a0190925292812091518255915160019182015586018054916114e183611d58565b91905055505050505050565b600060028601816114ff600185611d71565b81526020019081526020016000206001015490508084101561152357611523611ea5565b83811015611572576040805180820182528681526020808201878152600086815260028b01909252928120915182559151600191820155870180549161156883611d58565b9190505550610b17565b84600287016000611584600186611d71565b815260200190815260200160002060000160008282546115a49190611d84565b9091555050505050505050565b600080600084116115fd5760405162461bcd60e51b815260206004820152601660248201527504552433230536e617073686f743a20696420697320360541b60448201526064016104fd565b60cd5484111561164f5760405162461bcd60e51b815260206004820152601d60248201527f4552433230536e617073686f743a206e6f6e6578697374656e7420696400000060448201526064016104fd565b600061165b84866117e2565b845490915081036116735760008092509250506112c5565b600184600101828154811061168a5761168a611de5565b906000526020600020015492509250506112c5565b600054610100900460ff166116c65760405162461bcd60e51b81526004016104fd90611e43565b60366116d28382611f01565b5060376111dc8282611f01565b6001600160a01b0382166117355760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016104fd565b6117416000838361141d565b80603560008282546117539190611d84565b90915550506001600160a01b038216600081815260336020908152604080832080548601905551848152600080516020611fd6833981519152910160405180910390a35050565b6001600160a01b0383166117b9576117b18261188f565b6111dc6118b9565b6001600160a01b0382166117d0576117b18361188f565b6117d98361188f565b6111dc8261188f565b815460009081036117f5575060006104b7565b82546000905b8082101561184257600061180f83836118c9565b6000878152602090209091508590820154111561182e5780915061183c565b611839816001611d84565b92505b506117fb565b60008211801561186e57508361186b8661185d600186611d71565b600091825260209091200190565b54145b156118875761187e600183611d71565b925050506104b7565b5090506104b7565b6001600160a01b0381166000908152606560205260409020610787906118b483610b1f565b6118e4565b6118c760666118b460355490565b565b60006118d86002848418611fc1565b6108ea90848416611d84565b60006118ef60cd5490565b9050806118fb8461192f565b10156111dc578254600180820185556000858152602080822090930193909355938401805494850181558252902090910155565b8054600090810361194257506000919050565b8154829061195290600190611d71565b8154811061196257611962611de5565b90600052602060002001549050919050565b6000815180845260005b8181101561199a5760208185018101518683018201520161197e565b506000602082860101526020601f19601f83011685010191505092915050565b6020815260006108ea6020830184611974565b6001600160a01b038116811461078757600080fd5b600080604083850312156119f557600080fd5b8235611a00816119cd565b946020939093013593505050565b6000808284036080811215611a2257600080fd5b833592506060601f1982011215611a3857600080fd5b506020830190509250929050565b600080600060608486031215611a5b57600080fd5b8335611a66816119cd565b92506020840135611a76816119cd565b929592945050506040919091013590565b600060208284031215611a9957600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b6040805190810167ffffffffffffffff81118282101715611ad957611ad9611aa0565b60405290565b604051601f8201601f1916810167ffffffffffffffff81118282101715611b0857611b08611aa0565b604052919050565b600080600080600060a08688031215611b2857600080fd5b8535611b33816119cd565b9450602086810135611b44816119cd565b9450604087810135611b55816119cd565b945060608801359350608088013567ffffffffffffffff80821115611b7957600080fd5b818a0191508a601f830112611b8d57600080fd5b813581811115611b9f57611b9f611aa0565b611bad858260051b01611adf565b818152858101925060069190911b83018501908c821115611bcd57600080fd5b928501925b81841015611c165784848e031215611bea5760008081fd5b611bf2611ab6565b8435611bfd816119cd565b8152848701358782015283529284019291850191611bd2565b8096505050505050509295509295909350565b600060208284031215611c3b57600080fd5b81356108ea816119cd565b60008060408385031215611c5957600080fd5b8235611c64816119cd565b91506020830135611c74816119cd565b809150509250929050565b60008060008060608587031215611c9557600080fd5b843593506020850135611ca7816119cd565b9250604085013567ffffffffffffffff80821115611cc457600080fd5b818701915087601f830112611cd857600080fd5b813581811115611ce757600080fd5b886020828501011115611cf957600080fd5b95989497505060200194505050565b600181811c90821680611d1c57607f821691505b602082108103611d3c57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b600060018201611d6a57611d6a611d42565b5060010190565b818103818111156104b7576104b7611d42565b808201808211156104b7576104b7611d42565b634e487b7160e01b600052601260045260246000fd5b600082611dbc57611dbc611d97565b500690565b6001600160a01b038316815260406020820181905260009061137790830184611974565b634e487b7160e01b600052603260045260246000fd5b60008085851115611e0b57600080fd5b83861115611e1857600080fd5b5050820193919092039150565b803560208310156104b757600019602084900360031b1b1692915050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b600081611e9d57611e9d611d42565b506000190190565b634e487b7160e01b600052600160045260246000fd5b601f8211156111dc57600081815260208120601f850160051c81016020861015611ee25750805b601f850160051c820191505b81811015610b1757828155600101611eee565b815167ffffffffffffffff811115611f1b57611f1b611aa0565b611f2f81611f298454611d08565b84611ebb565b602080601f831160018114611f645760008415611f4c5750858301515b600019600386901b1c1916600185901b178555610b17565b600085815260208120601f198616915b82811015611f9357888601518255948401946001909101908401611f74565b5085821015611fb15787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b600082611fd057611fd0611d97565b50049056feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220ceae916e2fad24f9aaa4340b6994418d32d33c6ae1f266c50dd4ec5ccbdbce2764736f6c63430008130033"
+ } ==
+ List.first(coin_balances)
+ end
+
+ test "parses optimism coin balance and contract code" do
+ coin_balances = Importer.genesis_accounts(@optimism_genesis)
+
+ assert Enum.count(coin_balances) == 2
+
+ assert %{
+ address_hash: %Explorer.Chain.Hash{
+ byte_count: 20,
+ bytes: <<116, 214, 181, 2, 131, 172, 29, 101, 31, 154, 253, 195, 53, 33, 228, 193, 227, 51, 43, 120>>
+ },
+ value: 0,
+ contract_code:
+ "0x608060405234801561001057600080fd5b506004361061011d5760003560e01c8063245a7bfc14610122578063313ce5671461014657806350d25bcd1461016457806354fd4d501461017e57806358303b10146101865780636001ac53146101a5578063668a0f021461020f5780637284e4161461021757806379ba5097146102945780638205bf6a1461029e5780638da5cb5b146102a65780638f6b4d91146102ae57806392eefe9b146102b65780639a6fc8f5146102dc578063a928c09614610302578063b5ab58dc14610328578063b633620c14610345578063bc43cbaf14610362578063c15973041461036a578063e8c4be301461038b578063f2fde38b14610393578063f8a2abd3146103b9578063feaf968c146103df575b600080fd5b61012a6103e7565b604080516001600160a01b039092168252519081900360200190f35b61014e6103fc565b6040805160ff9092168252519081900360200190f35b61016c610480565b60408051918252519081900360200190f35b61016c610588565b61018e6105db565b6040805161ffff9092168252519081900360200190f35b6101cb600480360360208110156101bb57600080fd5b50356001600160501b03166105e5565b60405180866001600160501b03168152602001858152602001848152602001838152602001826001600160501b031681526020019550505050505060405180910390f35b61016c61074e565b61021f610850565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610259578181015183820152602001610241565b50505050905090810190601f1680156102865780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61029c610993565b005b61016c610a51565b61012a610b53565b6101cb610b62565b61029c600480360360208110156102cc57600080fd5b50356001600160a01b0316610cc9565b6101cb600480360360208110156102f257600080fd5b50356001600160501b0316610cf4565b61029c6004803603602081101561031857600080fd5b50356001600160a01b0316610dff565b61016c6004803603602081101561033e57600080fd5b5035610ed7565b61016c6004803603602081101561035b57600080fd5b5035610fe1565b61012a6110e4565b61012a6004803603602081101561038057600080fd5b503561ffff166110f3565b61012a611116565b61029c600480360360208110156103a957600080fd5b50356001600160a01b0316611125565b61029c600480360360208110156103cf57600080fd5b50356001600160a01b0316611181565b6101cb6111e3565b6004546201000090046001600160a01b031690565b6000600460000160029054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b15801561044f57600080fd5b505afa158015610463573d6000803e3d6000fd5b505050506040513d602081101561047957600080fd5b5051905090565b6005546000906001600160a01b031680158061053d575060408051630d629b5f60e31b815233600482018181526024830193845236604484018190526001600160a01b03861694636b14daf8946000939190606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b15801561051057600080fd5b505afa158015610524573d6000803e3d6000fd5b505050506040513d602081101561053a57600080fd5b50515b61057a576040805162461bcd60e51b81526020600482015260096024820152684e6f2061636365737360b81b604482015290519081900360640190fd5b6105826112ed565b91505090565b6000600460000160029054906101000a90046001600160a01b03166001600160a01b03166354fd4d506040518163ffffffff1660e01b815260040160206040518083038186803b15801561044f57600080fd5b60045461ffff1690565b60055460009081908190819081906001600160a01b03168015806106aa575060408051630d629b5f60e31b815233600482018181526024830193845236604484018190526001600160a01b03861694636b14daf8946000939190606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b15801561067d57600080fd5b505afa158015610691573d6000803e3d6000fd5b505050506040513d60208110156106a757600080fd5b50515b6106e7576040805162461bcd60e51b81526020600482015260096024820152684e6f2061636365737360b81b604482015290519081900360640190fd5b6002546001600160a01b0316610732576040805162461bcd60e51b815260206004820152601e6024820152600080516020611b2c833981519152604482015290519081900360640190fd5b61073b87611340565b939b929a50909850965090945092505050565b6005546000906001600160a01b031680158061080b575060408051630d629b5f60e31b815233600482018181526024830193845236604484018190526001600160a01b03861694636b14daf8946000939190606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b1580156107de57600080fd5b505afa1580156107f2573d6000803e3d6000fd5b505050506040513d602081101561080857600080fd5b50515b610848576040805162461bcd60e51b81526020600482015260096024820152684e6f2061636365737360b81b604482015290519081900360640190fd5b61058261143d565b6060600460000160029054906101000a90046001600160a01b03166001600160a01b0316637284e4166040518163ffffffff1660e01b815260040160006040518083038186803b1580156108a357600080fd5b505afa1580156108b7573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405260208110156108e057600080fd5b8101908080516040519392919084600160201b8211156108ff57600080fd5b90830190602082018581111561091457600080fd5b8251600160201b81118282018810171561092d57600080fd5b82525081516020918201929091019080838360005b8381101561095a578181015183820152602001610942565b50505050905090810190601f1680156109875780820380516001836020036101000a031916815260200191505b50604052505050905090565b61099b6114ec565b6001600160a01b0316336001600160a01b0316146109f9576040805162461bcd60e51b815260206004820152601660248201527526bab9ba10313290383937b837b9b2b21037bbb732b960511b604482015290519081900360640190fd5b6000610a036114fb565b9050610a0e3361150a565b610a18600061152c565b60405133906001600160a01b038316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a350565b6005546000906001600160a01b0316801580610b0e575060408051630d629b5f60e31b815233600482018181526024830193845236604484018190526001600160a01b03861694636b14daf8946000939190606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b158015610ae157600080fd5b505afa158015610af5573d6000803e3d6000fd5b505050506040513d6020811015610b0b57600080fd5b50515b610b4b576040805162461bcd60e51b81526020600482015260096024820152684e6f2061636365737360b81b604482015290519081900360640190fd5b61058261154e565b6000610b5d6114fb565b905090565b60055460009081908190819081906001600160a01b0316801580610c27575060408051630d629b5f60e31b815233600482018181526024830193845236604484018190526001600160a01b03861694636b14daf8946000939190606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b158015610bfa57600080fd5b505afa158015610c0e573d6000803e3d6000fd5b505050506040513d6020811015610c2457600080fd5b50515b610c64576040805162461bcd60e51b81526020600482015260096024820152684e6f2061636365737360b81b604482015290519081900360640190fd5b6002546001600160a01b0316610caf576040805162461bcd60e51b815260206004820152601e6024820152600080516020611b2c833981519152604482015290519081900360640190fd5b610cb76115a1565b95509550955095509550509091929394565b610cd233611697565b600580546001600160a01b0319166001600160a01b0392909216919091179055565b60055460009081908190819081906001600160a01b0316801580610db9575060408051630d629b5f60e31b815233600482018181526024830193845236604484018190526001600160a01b03861694636b14daf8946000939190606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b158015610d8c57600080fd5b505afa158015610da0573d6000803e3d6000fd5b505050506040513d6020811015610db657600080fd5b50515b610df6576040805162461bcd60e51b81526020600482015260096024820152684e6f2061636365737360b81b604482015290519081900360640190fd5b61073b87611700565b610e0833611697565b6002546001600160a01b03828116911614610e68576040805162461bcd60e51b815260206004820152601b60248201527a24b73b30b634b210383937b837b9b2b21030b3b3b932b3b0ba37b960291b604482015290519081900360640190fd5b600454600280546001600160a01b03191690556201000090046001600160a01b0316610e93826117f9565b816001600160a01b0316816001600160a01b03167f33745f67a407dcb785417f9c123dd3641479a102674b6e35c1f10975625b90e960405160405180910390a35050565b6005546000906001600160a01b0316801580610f94575060408051630d629b5f60e31b815233600482018181526024830193845236604484018190526001600160a01b03861694636b14daf8946000939190606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b158015610f6757600080fd5b505afa158015610f7b573d6000803e3d6000fd5b505050506040513d6020811015610f9157600080fd5b50515b610fd1576040805162461bcd60e51b81526020600482015260096024820152684e6f2061636365737360b81b604482015290519081900360640190fd5b610fda83611868565b9392505050565b6005546000906001600160a01b031680158061109e575060408051630d629b5f60e31b815233600482018181526024830193845236604484018190526001600160a01b03861694636b14daf8946000939190606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b15801561107157600080fd5b505afa158015611085573d6000803e3d6000fd5b505050506040513d602081101561109b57600080fd5b50515b6110db576040805162461bcd60e51b81526020600482015260096024820152684e6f2061636365737360b81b604482015290519081900360640190fd5b610fda83611942565b6005546001600160a01b031681565b61ffff81166000908152600360205260409020546001600160a01b03165b919050565b6002546001600160a01b031690565b61112e33611697565b6111378161152c565b806001600160a01b03166111496114fb565b6001600160a01b03167fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae127860405160405180910390a350565b61118a33611697565b600280546001600160a01b0319166001600160a01b0383811691821790925560045460405191926201000090910416907fc0f151710f03d713b71d9970cee0d5b11ddc9a7552abaa3f6ee818010f21600d90600090a350565b60055460009081908190819081906001600160a01b03168015806112a8575060408051630d629b5f60e31b815233600482018181526024830193845236604484018190526001600160a01b03861694636b14daf8946000939190606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b15801561127b57600080fd5b505afa15801561128f573d6000803e3d6000fd5b505050506040513d60208110156112a557600080fd5b50515b6112e5576040805162461bcd60e51b81526020600482015260096024820152684e6f2061636365737360b81b604482015290519081900360640190fd5b610cb76119e7565b6000600460000160029054906101000a90046001600160a01b03166001600160a01b03166350d25bcd6040518163ffffffff1660e01b815260040160206040518083038186803b15801561044f57600080fd5b60025460009081908190819081906001600160a01b0316611396576040805162461bcd60e51b815260206004820152601e6024820152600080516020611b2c833981519152604482015290519081900360640190fd5b60025460408051639a6fc8f560e01b81526001600160501b038916600482015290516001600160a01b0390921691639a6fc8f59160248082019260a092909190829003018186803b1580156113ea57600080fd5b505afa1580156113fe573d6000803e3d6000fd5b505050506040513d60a081101561141457600080fd5b508051602082015160408301516060840151608090940151929a91995097509195509350915050565b6000611447611b14565b506040805180820182526004805461ffff8116808452620100009091046001600160a01b031660208085018290528551633345078160e11b8152955194956114dd959394929363668a0f02938281019392829003018186803b1580156114ac57600080fd5b505afa1580156114c0573d6000803e3d6000fd5b505050506040513d60208110156114d657600080fd5b5051611abc565b6001600160501b031691505090565b6001546001600160a01b031690565b6000546001600160a01b031690565b600080546001600160a01b0319166001600160a01b0392909216919091179055565b600180546001600160a01b0319166001600160a01b0392909216919091179055565b6000600460000160029054906101000a90046001600160a01b03166001600160a01b0316638205bf6a6040518163ffffffff1660e01b815260040160206040518083038186803b15801561044f57600080fd5b60025460009081908190819081906001600160a01b03166115f7576040805162461bcd60e51b815260206004820152601e6024820152600080516020611b2c833981519152604482015290519081900360640190fd5b600260009054906101000a90046001600160a01b03166001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a06040518083038186803b15801561164557600080fd5b505afa158015611659573d6000803e3d6000fd5b505050506040513d60a081101561166f57600080fd5b5080516020820151604083015160608401516080909401519299919850965091945092509050565b61169f6114fb565b6001600160a01b0316816001600160a01b0316146116fd576040805162461bcd60e51b815260206004820152601660248201527527b7363c9031b0b63630b1363290313c9037bbb732b960511b604482015290519081900360640190fd5b50565b600080600080600080600061171d886001600160501b0316611ad6565b61ffff821660009081526003602052604090819020548151639a6fc8f560e01b81526001600160401b038416600482015291519395509193506001600160a01b0390911691639a6fc8f59160248082019260a092909190829003018186803b15801561178857600080fd5b505afa15801561179c573d6000803e3d6000fd5b505050506040513d60a08110156117b257600080fd5b508051602082015160408301516060840151608090940151929a50909850965090945092506117e5878787878787611ade565b939c929b5090995097509095509350505050565b60048054604080518082018252600161ffff80851691909101168082526001600160a01b0395909516602091820181905261ffff19909316851762010000600160b01b0319166201000084021790935560009384526003909252912080546001600160a01b0319169091179055565b60006001600160501b0382111561188157506000611111565b60008061188d84611ad6565b61ffff821660009081526003602052604090205491935091506001600160a01b0316806118c05760009350505050611111565b806001600160a01b031663b5ab58dc836040518263ffffffff1660e01b815260040180826001600160401b0316815260200191505060206040518083038186803b15801561190d57600080fd5b505afa158015611921573d6000803e3d6000fd5b505050506040513d602081101561193757600080fd5b505195945050505050565b60006001600160501b0382111561195b57506000611111565b60008061196784611ad6565b61ffff821660009081526003602052604090205491935091506001600160a01b03168061199a5760009350505050611111565b806001600160a01b031663b633620c836040518263ffffffff1660e01b815260040180826001600160401b0316815260200191505060206040518083038186803b15801561190d57600080fd5b60008060008060006119f7611b14565b506040805180820182526004805461ffff811683526201000090046001600160a01b0316602083018190528351633fabe5a360e21b815293519293909263feaf968c928281019260a0929190829003018186803b158015611a5757600080fd5b505afa158015611a6b573d6000803e3d6000fd5b505050506040513d60a0811015611a8157600080fd5b5080516020820151604083015160608401516080909401518551939a509198509650919450909250610cb79087908790879087908790611ade565b6001600160401b031660409190911b61ffff60401b161790565b604081901c91565b6000806000806000611af0868c611abc565b8a8a8a611afd8a8c611abc565b939f929e50909c509a509098509650505050505050565b60408051808201909152600080825260208201529056fe4e6f2070726f706f7365642061676772656761746f722070726573656e740000a2646970667358221220e01c79ee01ff8ee5600a342cb2ed15231329d7f3f090fbe7605e1dc18906a86964736f6c634300060c0033"
+ } ==
+ List.first(coin_balances)
+ end
end
describe "import_genesis_accounts/1" do
@@ -43,11 +87,11 @@ defmodule Explorer.ChainSpec.Geth.ImporterTest do
|> expect(:json_rpc, fn [
%{id: 0, jsonrpc: "2.0", method: "eth_getBlockByNumber", params: ["0x1", true]}
],
- _ ->
+ _opts ->
{:ok, [res]}
end)
- {:ok, %{address_coin_balances: address_coin_balances}} = Importer.import_genesis_accounts(@genesis)
+ {:ok, %{address_coin_balances: address_coin_balances}} = Importer.import_genesis_accounts(@geth_genesis)
assert Enum.count(address_coin_balances) == 3
assert CoinBalance |> Repo.all() |> Enum.count() == 3
@@ -86,7 +130,7 @@ defmodule Explorer.ChainSpec.Geth.ImporterTest do
assert to_string(address.contract_code) == code
end
- test "imports coin balances without 0x" do
+ test "imports polygon accounts" do
block_quantity = integer_to_quantity(1)
res = eth_block_number_fake_response(block_quantity)
@@ -94,16 +138,36 @@ defmodule Explorer.ChainSpec.Geth.ImporterTest do
|> expect(:json_rpc, fn [
%{id: 0, jsonrpc: "2.0", method: "eth_getBlockByNumber", params: ["0x1", true]}
],
- [] ->
+ _ ->
{:ok, [res]}
end)
- {:ok, %{address_coin_balances: address_coin_balances}} = Importer.import_genesis_accounts(@genesis)
+ {:ok, %{address_coin_balances: address_coin_balances}} = Importer.import_genesis_accounts(@polygon_genesis)
- assert Enum.count(address_coin_balances) == 3
- assert CoinBalance |> Repo.all() |> Enum.count() == 3
- assert CoinBalanceDaily |> Repo.all() |> Enum.count() == 3
- assert Address |> Repo.all() |> Enum.count() == 3
+ assert Enum.count(address_coin_balances) == 2
+ assert CoinBalance |> Repo.all() |> Enum.count() == 2
+ assert CoinBalanceDaily |> Repo.all() |> Enum.count() == 2
+ assert Address |> Repo.all() |> Enum.count() == 2
+ end
+
+ test "imports optimism accounts" do
+ block_quantity = integer_to_quantity(1)
+ res = eth_block_number_fake_response(block_quantity)
+
+ EthereumJSONRPC.Mox
+ |> expect(:json_rpc, fn [
+ %{id: 0, jsonrpc: "2.0", method: "eth_getBlockByNumber", params: ["0x1", true]}
+ ],
+ _ ->
+ {:ok, [res]}
+ end)
+
+ {:ok, %{address_coin_balances: address_coin_balances}} = Importer.import_genesis_accounts(@optimism_genesis)
+
+ assert Enum.count(address_coin_balances) == 2
+ assert CoinBalance |> Repo.all() |> Enum.count() == 2
+ assert CoinBalanceDaily |> Repo.all() |> Enum.count() == 2
+ assert Address |> Repo.all() |> Enum.count() == 2
end
end
diff --git a/apps/explorer/test/explorer/chain_spec/parity/importer_test.exs b/apps/explorer/test/explorer/chain_spec/parity/importer_test.exs
index 3f74ea550b67..e610fd661dcd 100644
--- a/apps/explorer/test/explorer/chain_spec/parity/importer_test.exs
+++ b/apps/explorer/test/explorer/chain_spec/parity/importer_test.exs
@@ -12,6 +12,8 @@ defmodule Explorer.ChainSpec.Parity.ImporterTest do
setup :set_mox_global
+ setup :verify_on_exit!
+
@chain_spec "#{File.cwd!()}/test/support/fixture/chain_spec/foundation.json"
|> File.read!()
|> Jason.decode!()
diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs
index 563e81d0a983..fb529bb074c6 100644
--- a/apps/explorer/test/explorer/chain_test.exs
+++ b/apps/explorer/test/explorer/chain_test.exs
@@ -23,19 +23,24 @@ defmodule Explorer.ChainTest do
Token,
TokenTransfer,
Transaction,
- SmartContract,
Wei
}
alias Explorer.{Chain, Etherscan}
+ alias Explorer.Chain.Address.Counters
alias Explorer.Chain.Cache.Block, as: BlockCache
alias Explorer.Chain.Cache.Transaction, as: TransactionCache
+ alias Explorer.Chain.Cache.PendingBlockOperation, as: PendingBlockOperationCache
alias Explorer.Chain.InternalTransaction.Type
alias Explorer.Chain.Supply.ProofOfAuthority
alias Explorer.Counters.AddressesWithBalanceCounter
alias Explorer.Counters.AddressesCounter
+ @first_topic_hex_string "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
+ @second_topic_hex_string "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"
+ @third_topic_hex_string "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d"
+
doctest Explorer.Chain
setup :set_mox_global
@@ -83,7 +88,7 @@ defmodule Explorer.ChainTest do
start_supervised!(AddressesWithBalanceCounter)
AddressesWithBalanceCounter.consolidate()
- addresses_with_balance = Chain.count_addresses_with_balance_from_cache()
+ addresses_with_balance = Counters.count_addresses_with_balance_from_cache()
assert is_integer(addresses_with_balance)
assert addresses_with_balance == 2
@@ -99,7 +104,7 @@ defmodule Explorer.ChainTest do
start_supervised!(AddressesCounter)
AddressesCounter.consolidate()
- addresses_with_balance = Chain.address_estimated_count()
+ addresses_with_balance = Counters.address_estimated_count()
assert is_integer(addresses_with_balance)
assert addresses_with_balance == 3
@@ -107,7 +112,7 @@ defmodule Explorer.ChainTest do
test "returns 0 on empty table" do
start_supervised!(AddressesCounter)
- assert 0 == Chain.address_estimated_count()
+ assert 0 == Counters.address_estimated_count()
end
end
@@ -181,30 +186,6 @@ defmodule Explorer.ChainTest do
assert result.token_contract_address_hash == token.contract_address_hash
end
- test "replaces existing token instance record" do
- token = insert(:token)
-
- params = %{
- token_id: 1,
- token_contract_address_hash: token.contract_address_hash,
- metadata: %{uri: "http://example.com"}
- }
-
- {:ok, _} = Chain.upsert_token_instance(params)
-
- params1 = %{
- token_id: 1,
- token_contract_address_hash: token.contract_address_hash,
- metadata: %{uri: "http://example1.com"}
- }
-
- {:ok, result} = Chain.upsert_token_instance(params1)
-
- assert result.token_id == Decimal.new(1)
- assert result.metadata == params1.metadata
- assert result.token_contract_address_hash == token.contract_address_hash
- end
-
test "fails to import with invalid params" do
params = %{
token_id: 1,
@@ -241,7 +222,8 @@ defmodule Explorer.ChainTest do
insert(:token_instance,
token_id: 1,
token_contract_address_hash: token.contract_address_hash,
- error: "no uri"
+ error: "no uri",
+ metadata: nil
)
params = %{
@@ -287,7 +269,7 @@ defmodule Explorer.ChainTest do
address: address
)
- assert Enum.count(Chain.address_to_logs(address_hash)) == 2
+ assert Enum.count(Chain.address_to_logs(address_hash, false)) == 2
end
test "paginates logs" do
@@ -298,571 +280,103 @@ defmodule Explorer.ChainTest do
|> insert(to_address: address)
|> with_block()
- log1 = insert(:log, transaction: transaction, index: 1, address: address, block_number: transaction.block_number)
+ _log1 = insert(:log, transaction: transaction, index: 1, address: address, block_number: transaction.block_number)
2..51
|> Enum.map(fn index ->
insert(:log,
block: transaction.block,
transaction: transaction,
- index: index,
- address: address,
- block_number: transaction.block_number
- )
- end)
- |> Enum.map(& &1.index)
-
- paging_options1 = %PagingOptions{page_size: 1}
-
- [_log] = Chain.address_to_logs(address_hash, paging_options: paging_options1)
-
- paging_options2 = %PagingOptions{page_size: 60, key: {transaction.block_number, transaction.index, log1.index}}
-
- assert Enum.count(Chain.address_to_logs(address_hash, paging_options: paging_options2)) == 50
- end
-
- test "searches logs by topic when the first topic matches" do
- %Address{hash: address_hash} = address = insert(:address)
-
- transaction1 =
- :transaction
- |> insert(to_address: address)
- |> with_block()
-
- insert(:log,
- block: transaction1.block,
- transaction: transaction1,
- index: 1,
- address: address,
- block_number: transaction1.block_number
- )
-
- transaction2 =
- :transaction
- |> insert(from_address: address)
- |> with_block()
-
- insert(:log,
- block: transaction2.block,
- transaction: transaction2,
- index: 2,
- address: address,
- first_topic: "test",
- block_number: transaction2.block_number
- )
-
- [found_log] = Chain.address_to_logs(address_hash, topic: "test")
-
- assert found_log.transaction.hash == transaction2.hash
- end
-
- test "searches logs by topic when the fourth topic matches" do
- %Address{hash: address_hash} = address = insert(:address)
-
- transaction1 =
- :transaction
- |> insert(to_address: address)
- |> with_block()
-
- insert(:log,
- block: transaction1.block,
- block_number: transaction1.block_number,
- transaction: transaction1,
- index: 1,
- address: address,
- fourth_topic: "test"
- )
-
- transaction2 =
- :transaction
- |> insert(from_address: address)
- |> with_block()
-
- insert(:log,
- block: transaction2.block,
- block_number: transaction2.block.number,
- transaction: transaction2,
- index: 2,
- address: address
- )
-
- [found_log] = Chain.address_to_logs(address_hash, topic: "test")
-
- assert found_log.transaction.hash == transaction1.hash
- end
- end
-
- describe "address_to_transactions_with_rewards/2" do
- test "without transactions" do
- %Address{hash: address_hash} = insert(:address)
-
- assert Repo.aggregate(Transaction, :count, :hash) == 0
-
- assert [] == Chain.address_to_transactions_with_rewards(address_hash)
- end
-
- test "with from transactions" do
- %Address{hash: address_hash} = address = insert(:address)
-
- transaction =
- :transaction
- |> insert(from_address: address)
- |> with_block()
-
- assert [transaction] ==
- Chain.address_to_transactions_with_rewards(address_hash, direction: :from)
- |> Repo.preload([:block, :to_address, :from_address])
- end
-
- test "with to transactions" do
- %Address{hash: address_hash} = address = insert(:address)
-
- transaction =
- :transaction
- |> insert(to_address: address)
- |> with_block()
-
- assert [transaction] ==
- Chain.address_to_transactions_with_rewards(address_hash, direction: :to)
- |> Repo.preload([:block, :to_address, :from_address])
- end
-
- test "with to and from transactions and direction: :from" do
- %Address{hash: address_hash} = address = insert(:address)
-
- transaction =
- :transaction
- |> insert(from_address: address)
- |> with_block()
-
- # only contains "from" transaction
- assert [transaction] ==
- Chain.address_to_transactions_with_rewards(address_hash, direction: :from)
- |> Repo.preload([:block, :to_address, :from_address])
- end
-
- test "with to and from transactions and direction: :to" do
- %Address{hash: address_hash} = address = insert(:address)
-
- transaction =
- :transaction
- |> insert(to_address: address)
- |> with_block()
-
- assert [transaction] ==
- Chain.address_to_transactions_with_rewards(address_hash, direction: :to)
- |> Repo.preload([:block, :to_address, :from_address])
- end
-
- test "with to and from transactions and no :direction option" do
- %Address{hash: address_hash} = address = insert(:address)
- block = insert(:block)
-
- transaction1 =
- :transaction
- |> insert(to_address: address)
- |> with_block(block)
-
- transaction2 =
- :transaction
- |> insert(from_address: address)
- |> with_block(block)
-
- assert [transaction2, transaction1] ==
- Chain.address_to_transactions_with_rewards(address_hash)
- |> Repo.preload([:block, :to_address, :from_address])
- end
-
- test "does not include non-contract-creation parent transactions" do
- transaction =
- %Transaction{} =
- :transaction
- |> insert()
- |> with_block()
-
- %InternalTransaction{created_contract_address: address} =
- insert(:internal_transaction_create,
- transaction: transaction,
- index: 0,
- block_number: transaction.block_number,
- block_hash: transaction.block_hash,
- block_index: 0,
- transaction_index: transaction.index
- )
-
- assert [] == Chain.address_to_transactions_with_rewards(address.hash)
- end
-
- test "returns transactions that have token transfers for the given to_address" do
- %Address{hash: address_hash} = address = insert(:address)
-
- transaction =
- :transaction
- |> insert(to_address: address, to_address_hash: address.hash)
- |> with_block()
-
- insert(
- :token_transfer,
- to_address: address,
- transaction: transaction
- )
-
- assert [transaction.hash] ==
- Chain.address_to_transactions_with_rewards(address_hash)
- |> Enum.map(& &1.hash)
- end
-
- test "with transactions can be paginated" do
- %Address{hash: address_hash} = address = insert(:address)
-
- second_page_hashes =
- 2
- |> insert_list(:transaction, from_address: address)
- |> with_block()
- |> Enum.map(& &1.hash)
-
- %Transaction{block_number: block_number, index: index} =
- :transaction
- |> insert(from_address: address)
- |> with_block()
-
- assert second_page_hashes ==
- address_hash
- |> Chain.address_to_transactions_with_rewards(
- paging_options: %PagingOptions{
- key: {block_number, index},
- page_size: 2
- }
- )
- |> Enum.map(& &1.hash)
- |> Enum.reverse()
- end
-
- test "returns results in reverse chronological order by block number and transaction index" do
- %Address{hash: address_hash} = address = insert(:address)
-
- a_block = insert(:block, number: 6000)
-
- %Transaction{hash: first} =
- :transaction
- |> insert(to_address: address)
- |> with_block(a_block)
-
- %Transaction{hash: second} =
- :transaction
- |> insert(to_address: address)
- |> with_block(a_block)
-
- %Transaction{hash: third} =
- :transaction
- |> insert(to_address: address)
- |> with_block(a_block)
-
- %Transaction{hash: fourth} =
- :transaction
- |> insert(to_address: address)
- |> with_block(a_block)
-
- b_block = insert(:block, number: 2000)
-
- %Transaction{hash: fifth} =
- :transaction
- |> insert(to_address: address)
- |> with_block(b_block)
-
- %Transaction{hash: sixth} =
- :transaction
- |> insert(to_address: address)
- |> with_block(b_block)
-
- result =
- address_hash
- |> Chain.address_to_transactions_with_rewards()
- |> Enum.map(& &1.hash)
-
- assert [fourth, third, second, first, sixth, fifth] == result
- end
-
- test "with emission rewards" do
- Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true)
-
- Application.put_env(:explorer, Explorer.Chain.Block.Reward,
- validators_contract_address: "0x0000000000000000000000000000000000000005",
- keys_manager_contract_address: "0x0000000000000000000000000000000000000006"
- )
-
- consumer_pid = start_supervised!(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand)
- :erlang.trace(consumer_pid, true, [:receive])
-
- block = insert(:block)
-
- block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower)
- block_miner_hash = block.miner_hash
-
- insert(
- :reward,
- address_hash: block.miner_hash,
- block_hash: block.hash,
- address_type: :validator
- )
-
- insert(
- :reward,
- address_hash: block.miner_hash,
- block_hash: block.hash,
- address_type: :emission_funds
- )
-
- # isValidator => true
- expect(
- EthereumJSONRPC.Mox,
- :json_rpc,
- fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
- {:ok,
- [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]}
- end
- )
-
- # getPayoutByMining => 0x0000000000000000000000000000000000000001
- expect(
- EthereumJSONRPC.Mox,
- :json_rpc,
- fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
- {:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]}
- end
- )
-
- res = Chain.address_to_transactions_with_rewards(block.miner.hash)
- assert [{_, _}] = res
-
- assert_receive {:trace, ^consumer_pid, :receive, {:"$gen_cast", {:fetch_or_update, ^block_miner_hash}}}, 1000
- :timer.sleep(500)
-
- on_exit(fn ->
- Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false)
-
- Application.put_env(:explorer, Explorer.Chain.Block.Reward,
- validators_contract_address: nil,
- keys_manager_contract_address: nil
- )
- end)
- end
-
- test "with emission rewards and transactions" do
- Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true)
-
- Application.put_env(:explorer, Explorer.Chain.Block.Reward,
- validators_contract_address: "0x0000000000000000000000000000000000000005",
- keys_manager_contract_address: "0x0000000000000000000000000000000000000006"
- )
-
- consumer_pid = start_supervised!(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand)
- :erlang.trace(consumer_pid, true, [:receive])
-
- block = insert(:block)
-
- block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower)
- block_miner_hash = block.miner_hash
-
- insert(
- :reward,
- address_hash: block.miner_hash,
- block_hash: block.hash,
- address_type: :validator
- )
-
- insert(
- :reward,
- address_hash: block.miner_hash,
- block_hash: block.hash,
- address_type: :emission_funds
- )
-
- :transaction
- |> insert(to_address: block.miner)
- |> with_block(block)
- |> Repo.preload(:token_transfers)
-
- # isValidator => true
- expect(
- EthereumJSONRPC.Mox,
- :json_rpc,
- fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
- {:ok,
- [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]}
- end
- )
-
- # getPayoutByMining => 0x0000000000000000000000000000000000000001
- expect(
- EthereumJSONRPC.Mox,
- :json_rpc,
- fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
- {:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]}
- end
- )
-
- assert [_, {_, _}] = Chain.address_to_transactions_with_rewards(block.miner.hash, direction: :to)
-
- assert_receive {:trace, ^consumer_pid, :receive, {:"$gen_cast", {:fetch_or_update, ^block_miner_hash}}}, 1000
- :timer.sleep(500)
-
- on_exit(fn ->
- Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false)
-
- Application.put_env(:explorer, Explorer.Chain.Block.Reward,
- validators_contract_address: nil,
- keys_manager_contract_address: nil
- )
- end)
- end
-
- test "with transactions if rewards are not in the range of blocks" do
- Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true)
-
- block = insert(:block)
-
- insert(
- :reward,
- address_hash: block.miner_hash,
- block_hash: block.hash,
- address_type: :validator
- )
-
- insert(
- :reward,
- address_hash: block.miner_hash,
- block_hash: block.hash,
- address_type: :emission_funds
- )
-
- :transaction
- |> insert(from_address: block.miner)
- |> with_block()
- |> Repo.preload(:token_transfers)
-
- assert [_] = Chain.address_to_transactions_with_rewards(block.miner.hash, direction: :from)
-
- Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false)
- end
-
- test "with emissions rewards, but feature disabled" do
- Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false)
-
- block = insert(:block)
-
- insert(
- :reward,
- address_hash: block.miner_hash,
- block_hash: block.hash,
- address_type: :validator
- )
-
- insert(
- :reward,
- address_hash: block.miner_hash,
- block_hash: block.hash,
- address_type: :emission_funds
- )
-
- assert [] == Chain.address_to_transactions_with_rewards(block.miner.hash)
- end
- end
-
- describe "address_to_transactions_tasks_range_of_blocks/2" do
- test "returns empty extremums if no transactions" do
- address = insert(:address)
-
- extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, [])
-
- assert extremums == %{
- :min_block_number => nil,
- :max_block_number => 0
- }
- end
+ index: index,
+ address: address,
+ block_number: transaction.block_number
+ )
+ end)
+ |> Enum.map(& &1.index)
- test "returns correct extremums for from_address" do
- address = insert(:address)
+ paging_options1 = %PagingOptions{page_size: 1}
- :transaction
- |> insert(from_address: address)
- |> with_block(insert(:block, number: 1000))
+ [_log] = Chain.address_to_logs(address_hash, false, paging_options: paging_options1)
- extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, [])
+ paging_options2 = %PagingOptions{page_size: 60, key: {transaction.block_number, 51}}
- assert extremums == %{
- :min_block_number => 1000,
- :max_block_number => 1000
- }
+ assert Enum.count(Chain.address_to_logs(address_hash, false, paging_options: paging_options2)) == 50
end
- test "returns correct extremums for to_address" do
- address = insert(:address)
+ test "searches logs by topic when the first topic matches" do
+ %Address{hash: address_hash} = address = insert(:address)
- :transaction
- |> insert(to_address: address)
- |> with_block(insert(:block, number: 1000))
+ transaction1 =
+ :transaction
+ |> insert(to_address: address)
+ |> with_block()
- extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, [])
+ insert(:log,
+ block: transaction1.block,
+ transaction: transaction1,
+ index: 1,
+ address: address,
+ block_number: transaction1.block_number
+ )
- assert extremums == %{
- :min_block_number => 1000,
- :max_block_number => 1000
- }
- end
+ first_topic_hex_string = "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65"
+ {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(first_topic_hex_string)
- test "returns correct extremums for created_contract_address" do
- address = insert(:address)
+ transaction2 =
+ :transaction
+ |> insert(from_address: address)
+ |> with_block()
- :transaction
- |> insert(created_contract_address: address)
- |> with_block(insert(:block, number: 1000))
+ insert(:log,
+ block: transaction2.block,
+ transaction: transaction2,
+ index: 2,
+ address: address,
+ first_topic: first_topic,
+ block_number: transaction2.block_number
+ )
- extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, [])
+ [found_log] = Chain.address_to_logs(address_hash, false, topic: first_topic_hex_string)
- assert extremums == %{
- :min_block_number => 1000,
- :max_block_number => 1000
- }
+ assert found_log.transaction.hash == transaction2.hash
end
- test "returns correct extremums for multiple number of transactions" do
- address = insert(:address)
-
- :transaction
- |> insert(created_contract_address: address)
- |> with_block(insert(:block, number: 1000))
-
- :transaction
- |> insert(created_contract_address: address)
- |> with_block(insert(:block, number: 999))
+ test "searches logs by topic when the fourth topic matches" do
+ %Address{hash: address_hash} = address = insert(:address)
- :transaction
- |> insert(created_contract_address: address)
- |> with_block(insert(:block, number: 1003))
+ transaction1 =
+ :transaction
+ |> insert(to_address: address)
+ |> with_block()
- :transaction
- |> insert(from_address: address)
- |> with_block(insert(:block, number: 1001))
+ fourth_topic_hex_string = "0x927abf391899d10d331079a63caffa905efa7075a44a7bbd52b190db4c4308fb"
+ {:ok, fourth_topic} = Explorer.Chain.Hash.Full.cast(fourth_topic_hex_string)
- :transaction
- |> insert(from_address: address)
- |> with_block(insert(:block, number: 1004))
+ insert(:log,
+ block: transaction1.block,
+ block_number: transaction1.block_number,
+ transaction: transaction1,
+ index: 1,
+ address: address,
+ fourth_topic: fourth_topic
+ )
- :transaction
- |> insert(to_address: address)
- |> with_block(insert(:block, number: 1002))
+ transaction2 =
+ :transaction
+ |> insert(from_address: address)
+ |> with_block()
- :transaction
- |> insert(to_address: address)
- |> with_block(insert(:block, number: 998))
+ insert(:log,
+ block: transaction2.block,
+ block_number: transaction2.block.number,
+ transaction: transaction2,
+ index: 2,
+ address: address
+ )
- extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, [])
+ [found_log] = Chain.address_to_logs(address_hash, false, topic: fourth_topic_hex_string)
- assert extremums == %{
- :min_block_number => 998,
- :max_block_number => 1004
- }
+ assert found_log.transaction.hash == transaction1.hash
end
end
@@ -874,7 +388,7 @@ defmodule Explorer.ChainTest do
|> insert(nonce: 100, from_address: address)
|> with_block(insert(:block, number: 1000))
- assert Chain.total_transactions_sent_by_address(address.hash) == 101
+ assert Counters.total_transactions_sent_by_address(address.hash) == 101
end
test "returns 0 when the address did not send transactions" do
@@ -884,7 +398,7 @@ defmodule Explorer.ChainTest do
|> insert(nonce: 100, to_address: address)
|> with_block(insert(:block, number: 1000))
- assert Chain.total_transactions_sent_by_address(address.hash) == 0
+ assert Counters.total_transactions_sent_by_address(address.hash) == 0
end
end
@@ -941,7 +455,7 @@ defmodule Explorer.ChainTest do
assert second_page_hashes ==
block.hash
- |> Chain.block_to_transactions(paging_options: %PagingOptions{key: {index}, page_size: 50})
+ |> Chain.block_to_transactions(paging_options: %PagingOptions{key: {block.number, index}, page_size: 50})
|> Enum.map(& &1.hash)
end
@@ -1098,13 +612,13 @@ defmodule Explorer.ChainTest do
test "without transactions" do
%Address{hash: address_hash} = insert(:address)
- assert Chain.address_to_incoming_transaction_count(address_hash) == 0
+ assert Counters.address_to_incoming_transaction_count(address_hash) == 0
end
test "with transactions" do
%Transaction{to_address: to_address} = insert(:transaction)
- assert Chain.address_to_incoming_transaction_count(to_address.hash) == 1
+ assert Counters.address_to_incoming_transaction_count(to_address.hash) == 1
end
end
@@ -1207,6 +721,12 @@ defmodule Explorer.ChainTest do
end
describe "finished_indexing_internal_transactions?/0" do
+ setup do
+ Supervisor.terminate_child(Explorer.Supervisor, PendingBlockOperationCache.child_id())
+ Supervisor.restart_child(Explorer.Supervisor, PendingBlockOperationCache.child_id())
+ on_exit(fn -> Supervisor.terminate_child(Explorer.Supervisor, PendingBlockOperationCache.child_id()) end)
+ end
+
test "finished indexing" do
block = insert(:block, number: 1)
@@ -1493,7 +1013,7 @@ defmodule Explorer.ChainTest do
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Block.child_id())
on_exit(fn ->
- Application.put_env(:indexer, :first_block, "")
+ Application.put_env(:indexer, :first_block, 0)
end)
end
@@ -1523,7 +1043,7 @@ defmodule Explorer.ChainTest do
end
test "returns 1.0 if fully indexed blocks starting from given FIRST_BLOCK" do
- Application.put_env(:indexer, :first_block, "5")
+ Application.put_env(:indexer, :first_block, 5)
for index <- 5..9 do
insert(:block, number: index, consensus: true)
@@ -1538,8 +1058,12 @@ defmodule Explorer.ChainTest do
describe "indexed_ratio_internal_transactions/0" do
setup do
+ Supervisor.terminate_child(Explorer.Supervisor, PendingBlockOperationCache.child_id())
+ Supervisor.restart_child(Explorer.Supervisor, PendingBlockOperationCache.child_id())
+
on_exit(fn ->
- Application.put_env(:indexer, :trace_first_block, "")
+ Application.put_env(:indexer, :trace_first_block, 0)
+ Supervisor.terminate_child(Explorer.Supervisor, PendingBlockOperationCache.child_id())
end)
end
@@ -1552,6 +1076,8 @@ defmodule Explorer.ChainTest do
end
end
+ Chain.indexed_ratio_internal_transactions()
+
assert Decimal.compare(Chain.indexed_ratio_internal_transactions(), Decimal.from_float(0.7)) == :eq
end
@@ -1568,7 +1094,7 @@ defmodule Explorer.ChainTest do
end
test "returns 1.0 if fully indexed blocks with internal transactions starting from given TRACE_FIRST_BLOCK" do
- Application.put_env(:indexer, :trace_first_block, "5")
+ Application.put_env(:indexer, :trace_first_block, 5)
for index <- 5..9 do
insert(:block, number: index)
@@ -1721,6 +1247,10 @@ defmodule Explorer.ChainTest do
# Full tests in `test/explorer/import_test.exs`
describe "import/1" do
+ {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(@first_topic_hex_string)
+ {:ok, second_topic} = Explorer.Chain.Hash.Full.cast(@second_topic_hex_string)
+ {:ok, third_topic} = Explorer.Chain.Hash.Full.cast(@third_topic_hex_string)
+
@import_data %{
blocks: %{
params: [
@@ -1776,13 +1306,12 @@ defmodule Explorer.ChainTest do
block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000",
- first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
- second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
- third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d",
+ first_topic: first_topic,
+ second_topic: second_topic,
+ third_topic: third_topic,
fourth_topic: nil,
index: 0,
- transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
- type: "mined"
+ transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5"
}
]
},
@@ -1845,6 +1374,9 @@ defmodule Explorer.ChainTest do
}
test "with valid data" do
+ {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(@first_topic_hex_string)
+ {:ok, second_topic} = Explorer.Chain.Hash.Full.cast(@second_topic_hex_string)
+ {:ok, third_topic} = Explorer.Chain.Hash.Full.cast(@third_topic_hex_string)
difficulty = Decimal.new(340_282_366_920_938_463_463_374_607_431_768_211_454)
total_difficulty = Decimal.new(12_590_447_576_074_723_148_144_860_474_975_121_280_509)
token_transfer_amount = Decimal.new(1_000_000_000_000_000_000)
@@ -1947,9 +1479,9 @@ defmodule Explorer.ChainTest do
167, 100, 0, 0>>
},
index: 0,
- first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
- second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
- third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d",
+ first_topic: ^first_topic,
+ second_topic: ^second_topic,
+ third_topic: ^third_topic,
fourth_topic: nil,
transaction_hash: %Hash{
byte_count: 32,
@@ -1957,7 +1489,6 @@ defmodule Explorer.ChainTest do
<<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57,
101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>>
},
- type: "mined",
inserted_at: %{},
updated_at: %{}
}
@@ -2067,76 +1598,6 @@ defmodule Explorer.ChainTest do
end
end
- describe "list_top_addresses/0" do
- test "without addresses with balance > 0" do
- insert(:address, fetched_coin_balance: 0)
- assert [] = Chain.list_top_addresses()
- end
-
- test "with top addresses in order" do
- address_hashes =
- 4..1
- |> Enum.map(&insert(:address, fetched_coin_balance: &1))
- |> Enum.map(& &1.hash)
-
- assert address_hashes ==
- Chain.list_top_addresses()
- |> Enum.map(fn {address, _transaction_count} -> address end)
- |> Enum.map(& &1.hash)
- end
-
- # flaky test
- # test "with top addresses in order with matching value" do
- # test_hashes =
- # 4..0
- # |> Enum.map(&Explorer.Chain.Hash.cast(Explorer.Chain.Hash.Address, &1))
- # |> Enum.map(&elem(&1, 1))
-
- # tail =
- # 4..1
- # |> Enum.map(&insert(:address, fetched_coin_balance: &1, hash: Enum.fetch!(test_hashes, &1 - 1)))
- # |> Enum.map(& &1.hash)
-
- # first_result_hash =
- # :address
- # |> insert(fetched_coin_balance: 4, hash: Enum.fetch!(test_hashes, 4))
- # |> Map.fetch!(:hash)
-
- # assert [first_result_hash | tail] ==
- # Chain.list_top_addresses()
- # |> Enum.map(fn {address, _transaction_count} -> address end)
- # |> Enum.map(& &1.hash)
- # end
-
- # flaky test
- # test "paginates addresses" do
- # test_hashes =
- # 4..0
- # |> Enum.map(&Explorer.Chain.Hash.cast(Explorer.Chain.Hash.Address, &1))
- # |> Enum.map(&elem(&1, 1))
-
- # result =
- # 4..1
- # |> Enum.map(&insert(:address, fetched_coin_balance: &1, hash: Enum.fetch!(test_hashes, &1 - 1)))
- # |> Enum.map(& &1.hash)
-
- # options = [paging_options: %PagingOptions{page_size: 1}]
-
- # [{top_address, _}] = Chain.list_top_addresses(options)
- # assert top_address.hash == List.first(result)
-
- # tail_options = [
- # paging_options: %PagingOptions{key: {top_address.fetched_coin_balance.value, top_address.hash}, page_size: 3}
- # ]
-
- # tail_result = tail_options |> Chain.list_top_addresses() |> Enum.map(fn {address, _} -> address.hash end)
-
- # [_ | expected_tail] = result
-
- # assert tail_result == expected_tail
- # end
- end
-
describe "stream_blocks_without_rewards/2" do
test "includes consensus blocks" do
%Block{hash: consensus_hash} = insert(:block, consensus: true)
@@ -2305,7 +1766,7 @@ defmodule Explorer.ChainTest do
%InternalTransaction{
from_address: %Ecto.Association.NotLoaded{},
to_address: %Ecto.Association.NotLoaded{},
- transaction: %Transaction{}
+ transaction: %Ecto.Association.NotLoaded{}
}
| _
] = Chain.address_to_internal_transactions(address_hash)
@@ -2765,7 +2226,7 @@ defmodule Explorer.ChainTest do
])
)
- assert internal_transaction.transaction.block_number == block.number
+ assert internal_transaction.block_number == block.number
end
test "with transaction with internal transactions loads associations with in necessity_by_association" do
@@ -2787,7 +2248,7 @@ defmodule Explorer.ChainTest do
%InternalTransaction{
from_address: %Ecto.Association.NotLoaded{},
to_address: %Ecto.Association.NotLoaded{},
- transaction: %Transaction{block: %Ecto.Association.NotLoaded{}}
+ transaction: %Ecto.Association.NotLoaded{}
}
] = Chain.transaction_to_internal_transactions(transaction.hash)
@@ -3039,7 +2500,7 @@ defmodule Explorer.ChainTest do
])
)
- assert internal_transaction.transaction.block_number == block.number
+ assert internal_transaction.block_number == block.number
end
test "with transaction with internal transactions loads associations with in necessity_by_association" do
@@ -3061,7 +2522,7 @@ defmodule Explorer.ChainTest do
%InternalTransaction{
from_address: %Ecto.Association.NotLoaded{},
to_address: %Ecto.Association.NotLoaded{},
- transaction: %Transaction{block: %Ecto.Association.NotLoaded{}}
+ transaction: %Ecto.Association.NotLoaded{}
}
] = Chain.all_transaction_to_internal_transactions(transaction.hash)
@@ -3686,67 +3147,29 @@ defmodule Explorer.ChainTest do
end
end
- describe "block_reward_by_parts/1" do
- setup do
- {:ok, emission_reward: insert(:emission_reward)}
- end
-
- test "without uncles", %{emission_reward: %{reward: reward, block_range: range}} do
- block = build(:block, number: range.from, base_fee_per_gas: 5, uncles: [])
-
- tx1 = build(:transaction, gas_price: 1, gas_used: 1, block_number: block.number, block_hash: block.hash)
- tx2 = build(:transaction, gas_price: 1, gas_used: 2, block_number: block.number, block_hash: block.hash)
-
- tx3 =
- build(:transaction,
- gas_price: 1,
- gas_used: 3,
- block_number: block.number,
- block_hash: block.hash,
- max_priority_fee_per_gas: 1
- )
-
- expected_txn_fees = %Wei{value: Decimal.new(6)}
- expected_burned_fees = %Wei{value: Decimal.new(30)}
- expected_uncle_reward = %Wei{value: Decimal.new(0)}
-
- assert %{
- static_reward: ^reward,
- txn_fees: ^expected_txn_fees,
- burned_fees: ^expected_burned_fees,
- uncle_reward: ^expected_uncle_reward
- } = Chain.block_reward_by_parts(block, [tx1, tx2, tx3])
- end
-
- test "with uncles", %{emission_reward: %{reward: reward, block_range: range}} do
- block =
- build(:block, number: range.from, uncles: ["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273311"])
-
- expected_uncle_reward = Wei.mult(reward, Decimal.from_float(1 / 32))
-
- assert %{uncle_reward: ^expected_uncle_reward} = Chain.block_reward_by_parts(block, [])
- end
- end
-
describe "gas_payment_by_block_hash/1" do
setup do
number = 1
- %{consensus_block: insert(:block, number: number, consensus: true), number: number}
+ block = insert(:block, number: number, consensus: true)
+
+ %{consensus_block: block, number: number}
end
- test "without consensus block hash has no key", %{consensus_block: consensus_block, number: number} do
+ test "without consensus block hash has key with 0 value", %{consensus_block: consensus_block, number: number} do
non_consensus_block = insert(:block, number: number, consensus: false)
:transaction
- |> insert(gas_price: 1)
+ |> insert(gas_price: 1, block_consensus: false)
|> with_block(consensus_block, gas_used: 1)
:transaction
- |> insert(gas_price: 1)
+ |> insert(gas_price: 1, block_consensus: false)
|> with_block(consensus_block, gas_used: 2)
- assert Chain.gas_payment_by_block_hash([non_consensus_block.hash]) == %{}
+ assert Chain.gas_payment_by_block_hash([non_consensus_block.hash]) == %{
+ non_consensus_block.hash => %Wei{value: Decimal.new(0)}
+ }
end
test "with consensus block hash without transactions has key with 0 value", %{
@@ -4021,289 +3444,27 @@ defmodule Explorer.ChainTest do
)
)
- assert decompiled_smart_contract.decompiled_source_code == code
- end
-
- test "creates two smart contracts for different decompiler versions" do
- inserted_decompiled_smart_contract = insert(:decompiled_smart_contract)
- code = "code2"
- version = "2"
-
- {:ok, _decompiled_smart_contract} =
- Chain.create_decompiled_smart_contract(%{
- decompiler_version: version,
- decompiled_source_code: code,
- address_hash: inserted_decompiled_smart_contract.address_hash
- })
-
- decompiled_smart_contracts =
- Repo.all(
- from(ds in DecompiledSmartContract, where: ds.address_hash == ^inserted_decompiled_smart_contract.address_hash)
- )
-
- assert Enum.count(decompiled_smart_contracts) == 2
- end
- end
-
- describe "create_smart_contract/1" do
- setup do
- smart_contract_bytecode =
- "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029"
-
- created_contract_address =
- insert(
- :address,
- hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
- contract_code: smart_contract_bytecode
- )
-
- transaction =
- :transaction
- |> insert()
- |> with_block()
-
- insert(
- :internal_transaction_create,
- transaction: transaction,
- index: 0,
- created_contract_address: created_contract_address,
- created_contract_code: smart_contract_bytecode,
- block_number: transaction.block_number,
- block_hash: transaction.block_hash,
- block_index: 0,
- transaction_index: transaction.index
- )
-
- valid_attrs = %{
- address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
- name: "SimpleStorage",
- compiler_version: "0.4.23",
- optimization: false,
- contract_source_code:
- "pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }",
- abi: [
- %{
- "constant" => false,
- "inputs" => [%{"name" => "x", "type" => "uint256"}],
- "name" => "set",
- "outputs" => [],
- "payable" => false,
- "stateMutability" => "nonpayable",
- "type" => "function"
- },
- %{
- "constant" => true,
- "inputs" => [],
- "name" => "get",
- "outputs" => [%{"name" => "", "type" => "uint256"}],
- "payable" => false,
- "stateMutability" => "view",
- "type" => "function"
- }
- ]
- }
-
- {:ok, valid_attrs: valid_attrs, address: created_contract_address}
- end
-
- test "with valid data creates a smart contract", %{valid_attrs: valid_attrs} do
- assert {:ok, %SmartContract{} = smart_contract} = Chain.create_smart_contract(valid_attrs)
- assert smart_contract.name == "SimpleStorage"
- assert smart_contract.compiler_version == "0.4.23"
- assert smart_contract.optimization == false
- assert smart_contract.contract_source_code != ""
- assert smart_contract.abi != ""
-
- assert Repo.get_by(
- Address.Name,
- address_hash: smart_contract.address_hash,
- name: smart_contract.name,
- primary: true
- )
- end
-
- test "clears an existing primary name and sets the new one", %{valid_attrs: valid_attrs, address: address} do
- insert(:address_name, address: address, primary: true)
- assert {:ok, %SmartContract{} = smart_contract} = Chain.create_smart_contract(valid_attrs)
-
- assert Repo.get_by(
- Address.Name,
- address_hash: smart_contract.address_hash,
- name: smart_contract.name,
- primary: true
- )
- end
-
- test "trims whitespace from address name", %{valid_attrs: valid_attrs} do
- attrs = %{valid_attrs | name: " SimpleStorage "}
- assert {:ok, _} = Chain.create_smart_contract(attrs)
- assert Repo.get_by(Address.Name, name: "SimpleStorage")
- end
-
- test "sets the address verified field to true", %{valid_attrs: valid_attrs} do
- assert {:ok, %SmartContract{} = smart_contract} = Chain.create_smart_contract(valid_attrs)
-
- assert Repo.get_by(Address, hash: smart_contract.address_hash).verified == true
- end
- end
-
- describe "update_smart_contract/1" do
- setup do
- smart_contract_bytecode =
- "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029"
-
- created_contract_address =
- insert(
- :address,
- hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
- contract_code: smart_contract_bytecode
- )
-
- transaction =
- :transaction
- |> insert()
- |> with_block()
-
- insert(
- :internal_transaction_create,
- transaction: transaction,
- index: 0,
- created_contract_address: created_contract_address,
- created_contract_code: smart_contract_bytecode,
- block_number: transaction.block_number,
- block_hash: transaction.block_hash,
- block_index: 0,
- transaction_index: transaction.index
- )
-
- valid_attrs = %{
- address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
- name: "SimpleStorage",
- compiler_version: "0.4.23",
- optimization: false,
- contract_source_code:
- "pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }",
- abi: [
- %{
- "constant" => false,
- "inputs" => [%{"name" => "x", "type" => "uint256"}],
- "name" => "set",
- "outputs" => [],
- "payable" => false,
- "stateMutability" => "nonpayable",
- "type" => "function"
- },
- %{
- "constant" => true,
- "inputs" => [],
- "name" => "get",
- "outputs" => [%{"name" => "", "type" => "uint256"}],
- "payable" => false,
- "stateMutability" => "view",
- "type" => "function"
- }
- ],
- partially_verified: true
- }
-
- secondary_sources = [
- %{
- file_name: "storage.sol",
- contract_source_code:
- "pragma solidity >=0.7.0 <0.9.0;contract Storage {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
- address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
- },
- %{
- file_name: "storage_1.sol",
- contract_source_code:
- "pragma solidity >=0.7.0 <0.9.0;contract Storage_1 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
- address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
- }
- ]
-
- changed_sources = [
- %{
- file_name: "storage_2.sol",
- contract_source_code:
- "pragma solidity >=0.7.0 <0.9.0;contract Storage_2 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
- address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
- },
- %{
- file_name: "storage_3.sol",
- contract_source_code:
- "pragma solidity >=0.7.0 <0.9.0;contract Storage_3 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
- address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
- }
- ]
-
- _ = Chain.create_smart_contract(valid_attrs, [], secondary_sources)
-
- {:ok,
- valid_attrs: valid_attrs,
- address: created_contract_address,
- secondary_sources: secondary_sources,
- changed_sources: changed_sources}
- end
-
- test "change partially_verified field", %{valid_attrs: valid_attrs, address: address} do
- sc_before_call = Repo.get_by(SmartContract, address_hash: address.hash)
- assert sc_before_call.name == Map.get(valid_attrs, :name)
- assert sc_before_call.partially_verified == Map.get(valid_attrs, :partially_verified)
-
- assert {:ok, %SmartContract{}} =
- Chain.update_smart_contract(%{address_hash: address.hash, partially_verified: false})
-
- sc_after_call = Repo.get_by(SmartContract, address_hash: address.hash)
- assert sc_after_call.name == Map.get(valid_attrs, :name)
- assert sc_after_call.partially_verified == false
- assert sc_after_call.compiler_version == Map.get(valid_attrs, :compiler_version)
- assert sc_after_call.optimization == Map.get(valid_attrs, :optimization)
- assert sc_after_call.contract_source_code == Map.get(valid_attrs, :contract_source_code)
- end
-
- test "check nothing changed", %{valid_attrs: valid_attrs, address: address} do
- sc_before_call = Repo.get_by(SmartContract, address_hash: address.hash)
- assert sc_before_call.name == Map.get(valid_attrs, :name)
- assert sc_before_call.partially_verified == Map.get(valid_attrs, :partially_verified)
-
- assert {:ok, %SmartContract{}} = Chain.update_smart_contract(%{address_hash: address.hash})
-
- sc_after_call = Repo.get_by(SmartContract, address_hash: address.hash)
- assert sc_after_call.name == Map.get(valid_attrs, :name)
- assert sc_after_call.partially_verified == Map.get(valid_attrs, :partially_verified)
- assert sc_after_call.compiler_version == Map.get(valid_attrs, :compiler_version)
- assert sc_after_call.optimization == Map.get(valid_attrs, :optimization)
- assert sc_after_call.contract_source_code == Map.get(valid_attrs, :contract_source_code)
+ assert decompiled_smart_contract.decompiled_source_code == code
end
- test "check additional sources update", %{
- address: address,
- secondary_sources: secondary_sources,
- changed_sources: changed_sources
- } do
- sc_before_call = Repo.get_by(Address, hash: address.hash) |> Repo.preload(:smart_contract_additional_sources)
-
- assert sc_before_call.smart_contract_additional_sources
- |> Enum.with_index()
- |> Enum.all?(fn {el, ind} ->
- {:ok, src} = Enum.fetch(secondary_sources, ind)
-
- el.file_name == Map.get(src, :file_name) and
- el.contract_source_code == Map.get(src, :contract_source_code)
- end)
-
- assert {:ok, %SmartContract{}} = Chain.update_smart_contract(%{address_hash: address.hash}, [], changed_sources)
+ test "creates two smart contracts for different decompiler versions" do
+ inserted_decompiled_smart_contract = insert(:decompiled_smart_contract)
+ code = "code2"
+ version = "2"
- sc_after_call = Repo.get_by(Address, hash: address.hash) |> Repo.preload(:smart_contract_additional_sources)
+ {:ok, _decompiled_smart_contract} =
+ Chain.create_decompiled_smart_contract(%{
+ decompiler_version: version,
+ decompiled_source_code: code,
+ address_hash: inserted_decompiled_smart_contract.address_hash
+ })
- assert sc_after_call.smart_contract_additional_sources
- |> Enum.with_index()
- |> Enum.all?(fn {el, ind} ->
- {:ok, src} = Enum.fetch(changed_sources, ind)
+ decompiled_smart_contracts =
+ Repo.all(
+ from(ds in DecompiledSmartContract, where: ds.address_hash == ^inserted_decompiled_smart_contract.address_hash)
+ )
- el.file_name == Map.get(src, :file_name) and
- el.contract_source_code == Map.get(src, :contract_source_code)
- end)
+ assert Enum.count(decompiled_smart_contracts) == 2
end
end
@@ -4821,14 +3982,6 @@ defmodule Explorer.ChainTest do
assert Chain.circulating_supply() == ProofOfAuthority.circulating()
end
- describe "address_hash_to_smart_contract/1" do
- test "fetches a smart contract" do
- smart_contract = insert(:smart_contract, contract_code_md5: "123")
-
- assert ^smart_contract = Chain.address_hash_to_smart_contract(smart_contract.address_hash)
- end
- end
-
describe "token_from_address_hash/1" do
test "with valid hash" do
token = insert(:token)
@@ -4889,118 +4042,6 @@ defmodule Explorer.ChainTest do
end
end
- describe "stream_unfetched_token_instances/2" do
- test "reduces with given reducer and accumulator for ERC-721 token" do
- token_contract_address = insert(:contract_address)
- token = insert(:token, contract_address: token_contract_address, type: "ERC-721")
-
- transaction =
- :transaction
- |> insert()
- |> with_block(insert(:block, number: 1))
-
- token_transfer =
- insert(
- :token_transfer,
- block_number: 1000,
- to_address: build(:address),
- transaction: transaction,
- token_contract_address: token_contract_address,
- token: token,
- token_ids: [11]
- )
-
- assert {:ok, [result]} = Chain.stream_unfetched_token_instances([], &[&1 | &2])
- assert result.token_id == List.first(token_transfer.token_ids)
- assert result.contract_address_hash == token_transfer.token_contract_address_hash
- end
-
- test "does not fetch token transfers without token_ids" do
- token_contract_address = insert(:contract_address)
- token = insert(:token, contract_address: token_contract_address, type: "ERC-721")
-
- transaction =
- :transaction
- |> insert()
- |> with_block(insert(:block, number: 1))
-
- insert(
- :token_transfer,
- block_number: 1000,
- to_address: build(:address),
- transaction: transaction,
- token_contract_address: token_contract_address,
- token: token,
- token_ids: nil
- )
-
- assert {:ok, []} = Chain.stream_unfetched_token_instances([], &[&1 | &2])
- end
-
- test "do not fetch records with token instances" do
- token_contract_address = insert(:contract_address)
- token = insert(:token, contract_address: token_contract_address, type: "ERC-721")
-
- transaction =
- :transaction
- |> insert()
- |> with_block(insert(:block, number: 1))
-
- token_transfer =
- insert(
- :token_transfer,
- block_number: 1000,
- to_address: build(:address),
- transaction: transaction,
- token_contract_address: token_contract_address,
- token: token,
- token_ids: [11]
- )
-
- insert(:token_instance,
- token_id: List.first(token_transfer.token_ids),
- token_contract_address_hash: token_transfer.token_contract_address_hash
- )
-
- assert {:ok, []} = Chain.stream_unfetched_token_instances([], &[&1 | &2])
- end
- end
-
- describe "search_token/1" do
- test "finds by part of the name" do
- token = insert(:token, name: "magic token", symbol: "MAGIC")
-
- [result] = Chain.search_token("magic")
-
- assert result.link == token.contract_address_hash
- end
-
- test "finds multiple results in different columns" do
- insert(:token, name: "magic token", symbol: "TOKEN")
- insert(:token, name: "token", symbol: "MAGIC")
-
- result = Chain.search_token("magic")
-
- assert Enum.count(result) == 2
- end
-
- test "do not returns wrong tokens" do
- insert(:token, name: "token", symbol: "TOKEN")
-
- result = Chain.search_token("magic")
-
- assert Enum.empty?(result)
- end
-
- test "finds record by the term in the second word" do
- insert(:token, name: "token magic", symbol: "TOKEN")
-
- result = Chain.search_token("magic")
-
- assert Enum.count(result) == 1
- end
- end
-
describe "transaction_has_token_transfers?/1" do
test "returns true if transaction has token transfers" do
transaction = insert(:transaction)
@@ -5102,7 +4143,7 @@ defmodule Explorer.ChainTest do
token_balances =
address.hash
|> Chain.fetch_last_token_balances()
- |> Enum.map(fn {token_balance, _} -> token_balance.address_hash end)
+ |> Enum.map(fn token_balance -> token_balance.address_hash end)
assert token_balances == [current_token_balance.address_hash]
end
@@ -5333,37 +4374,13 @@ defmodule Explorer.ChainTest do
unique_tokens_ids_paginated =
token_contract_address.hash
- |> Chain.address_to_unique_tokens(paging_options: paging_options)
+ |> Chain.address_to_unique_tokens(token, paging_options: paging_options)
|> Enum.map(& &1.token_id)
assert unique_tokens_ids_paginated == [List.first(second_page.token_ids)]
end
end
- describe "uncataloged_token_transfer_block_numbers/0" do
- test "returns a list of block numbers" do
- block = insert(:block)
- address = insert(:address)
-
- log =
- insert(:token_transfer_log,
- transaction:
- insert(:transaction,
- block_number: block.number,
- block_hash: block.hash,
- cumulative_gas_used: 0,
- gas_used: 0,
- index: 0
- ),
- block: block,
- address_hash: address.hash
- )
-
- block_number = log.block_number
- assert {:ok, [^block_number]} = Chain.uncataloged_token_transfer_block_numbers()
- end
- end
-
describe "address_to_balances_by_day/1" do
test "return a list of balances by day" do
address = insert(:address)
@@ -5777,433 +4794,4 @@ defmodule Explorer.ChainTest do
assert Chain.transaction_to_revert_reason(transaction) == "No credit of that type"
end
end
-
- describe "verified_contracts/2" do
- test "without contracts" do
- assert [] = Chain.verified_contracts()
- end
-
- test "with contracts" do
- %SmartContract{address_hash: hash} = insert(:smart_contract)
-
- assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts()
- end
-
- test "with contracts can be paginated" do
- second_page_contracts_ids =
- 50
- |> insert_list(:smart_contract)
- |> Enum.map(& &1.id)
-
- contract = insert(:smart_contract)
-
- assert second_page_contracts_ids ==
- [paging_options: %PagingOptions{key: {contract.id}, page_size: 50}]
- |> Chain.verified_contracts()
- |> Enum.map(& &1.id)
- |> Enum.reverse()
- end
-
- test "filters solidity" do
- insert(:smart_contract, is_vyper_contract: true)
- %SmartContract{address_hash: hash} = insert(:smart_contract, is_vyper_contract: false)
-
- assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts(filter: :solidity)
- end
-
- test "filters vyper" do
- insert(:smart_contract, is_vyper_contract: false)
- %SmartContract{address_hash: hash} = insert(:smart_contract, is_vyper_contract: true)
-
- assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts(filter: :vyper)
- end
-
- test "search by address" do
- insert(:smart_contract)
- insert(:smart_contract)
- insert(:smart_contract)
- %SmartContract{address_hash: hash} = insert(:smart_contract)
-
- assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts(search: Hash.to_string(hash))
- end
-
- test "search by name" do
- insert(:smart_contract)
- insert(:smart_contract)
- insert(:smart_contract)
- contract_name = "qwertyufhgkhiop"
- %SmartContract{address_hash: hash} = insert(:smart_contract, name: contract_name)
-
- assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts(search: contract_name)
- end
- end
-
- describe "proxy contracts features" do
- @proxy_abi [
- %{
- "type" => "function",
- "stateMutability" => "nonpayable",
- "payable" => false,
- "outputs" => [%{"type" => "bool", "name" => ""}],
- "name" => "upgradeTo",
- "inputs" => [%{"type" => "address", "name" => "newImplementation"}],
- "constant" => false
- },
- %{
- "type" => "function",
- "stateMutability" => "view",
- "payable" => false,
- "outputs" => [%{"type" => "uint256", "name" => ""}],
- "name" => "version",
- "inputs" => [],
- "constant" => true
- },
- %{
- "type" => "function",
- "stateMutability" => "view",
- "payable" => false,
- "outputs" => [%{"type" => "address", "name" => ""}],
- "name" => "implementation",
- "inputs" => [],
- "constant" => true
- },
- %{
- "type" => "function",
- "stateMutability" => "nonpayable",
- "payable" => false,
- "outputs" => [],
- "name" => "renounceOwnership",
- "inputs" => [],
- "constant" => false
- },
- %{
- "type" => "function",
- "stateMutability" => "view",
- "payable" => false,
- "outputs" => [%{"type" => "address", "name" => ""}],
- "name" => "getOwner",
- "inputs" => [],
- "constant" => true
- },
- %{
- "type" => "function",
- "stateMutability" => "view",
- "payable" => false,
- "outputs" => [%{"type" => "address", "name" => ""}],
- "name" => "getProxyStorage",
- "inputs" => [],
- "constant" => true
- },
- %{
- "type" => "function",
- "stateMutability" => "nonpayable",
- "payable" => false,
- "outputs" => [],
- "name" => "transferOwnership",
- "inputs" => [%{"type" => "address", "name" => "_newOwner"}],
- "constant" => false
- },
- %{
- "type" => "constructor",
- "stateMutability" => "nonpayable",
- "payable" => false,
- "inputs" => [
- %{"type" => "address", "name" => "_proxyStorage"},
- %{"type" => "address", "name" => "_implementationAddress"}
- ]
- },
- %{"type" => "fallback", "stateMutability" => "nonpayable", "payable" => false},
- %{
- "type" => "event",
- "name" => "Upgraded",
- "inputs" => [
- %{"type" => "uint256", "name" => "version", "indexed" => false},
- %{"type" => "address", "name" => "implementation", "indexed" => true}
- ],
- "anonymous" => false
- },
- %{
- "type" => "event",
- "name" => "OwnershipRenounced",
- "inputs" => [%{"type" => "address", "name" => "previousOwner", "indexed" => true}],
- "anonymous" => false
- },
- %{
- "type" => "event",
- "name" => "OwnershipTransferred",
- "inputs" => [
- %{"type" => "address", "name" => "previousOwner", "indexed" => true},
- %{"type" => "address", "name" => "newOwner", "indexed" => true}
- ],
- "anonymous" => false
- }
- ]
-
- @implementation_abi [
- %{
- "constant" => false,
- "inputs" => [%{"name" => "x", "type" => "uint256"}],
- "name" => "set",
- "outputs" => [],
- "payable" => false,
- "stateMutability" => "nonpayable",
- "type" => "function"
- },
- %{
- "constant" => true,
- "inputs" => [],
- "name" => "get",
- "outputs" => [%{"name" => "", "type" => "uint256"}],
- "payable" => false,
- "stateMutability" => "view",
- "type" => "function"
- }
- ]
-
- test "combine_proxy_implementation_abi/2 returns empty [] abi if proxy abi is null" do
- proxy_contract_address = insert(:contract_address)
-
- assert Chain.combine_proxy_implementation_abi(%SmartContract{address_hash: proxy_contract_address.hash, abi: nil}) ==
- []
- end
-
- test "combine_proxy_implementation_abi/2 returns [] abi for unverified proxy" do
- proxy_contract_address = insert(:contract_address)
-
- smart_contract =
- insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123")
-
- get_eip1967_implementation()
-
- assert Chain.combine_proxy_implementation_abi(smart_contract) == []
- end
-
- test "combine_proxy_implementation_abi/2 returns proxy abi if implementation is not verified" do
- proxy_contract_address = insert(:contract_address)
-
- smart_contract =
- insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
-
- assert Chain.combine_proxy_implementation_abi(smart_contract) == @proxy_abi
- end
-
- test "combine_proxy_implementation_abi/2 returns proxy + implementation abi if implementation is verified" do
- proxy_contract_address = insert(:contract_address)
-
- smart_contract =
- insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
-
- implementation_contract_address = insert(:contract_address)
-
- insert(:smart_contract,
- address_hash: implementation_contract_address.hash,
- abi: @implementation_abi,
- contract_code_md5: "123"
- )
-
- implementation_contract_address_hash_string =
- Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
-
- expect(
- EthereumJSONRPC.Mox,
- :json_rpc,
- fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
- {:ok,
- [
- %{
- id: id,
- jsonrpc: "2.0",
- result: "0x000000000000000000000000" <> implementation_contract_address_hash_string
- }
- ]}
- end
- )
-
- combined_abi = Chain.combine_proxy_implementation_abi(smart_contract)
-
- assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == false
- assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == false
- assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == true
- assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == true
- end
-
- test "get_implementation_abi_from_proxy/2 returns empty [] abi if proxy abi is null" do
- proxy_contract_address = insert(:contract_address)
-
- assert Chain.get_implementation_abi_from_proxy(
- %SmartContract{address_hash: proxy_contract_address.hash, abi: nil},
- []
- ) ==
- []
- end
-
- test "get_implementation_abi_from_proxy/2 returns [] abi for unverified proxy" do
- proxy_contract_address = insert(:contract_address)
-
- smart_contract =
- insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123")
-
- get_eip1967_implementation()
-
- assert Chain.combine_proxy_implementation_abi(smart_contract) == []
- end
-
- test "get_implementation_abi_from_proxy/2 returns [] if implementation is not verified" do
- proxy_contract_address = insert(:contract_address)
-
- smart_contract =
- insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
-
- assert Chain.get_implementation_abi_from_proxy(smart_contract, []) == []
- end
-
- test "get_implementation_abi_from_proxy/2 returns implementation abi if implementation is verified" do
- proxy_contract_address = insert(:contract_address)
-
- smart_contract =
- insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
-
- implementation_contract_address = insert(:contract_address)
-
- insert(:smart_contract,
- address_hash: implementation_contract_address.hash,
- abi: @implementation_abi,
- contract_code_md5: "123"
- )
-
- implementation_contract_address_hash_string =
- Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
-
- expect(
- EthereumJSONRPC.Mox,
- :json_rpc,
- fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
- {:ok,
- [
- %{
- id: id,
- jsonrpc: "2.0",
- result: "0x000000000000000000000000" <> implementation_contract_address_hash_string
- }
- ]}
- end
- )
-
- implementation_abi = Chain.get_implementation_abi_from_proxy(smart_contract, [])
-
- assert implementation_abi == @implementation_abi
- end
-
- test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern" do
- proxy_contract_address = insert(:contract_address)
-
- smart_contract =
- insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123")
-
- implementation_contract_address = insert(:contract_address)
-
- insert(:smart_contract,
- address_hash: implementation_contract_address.hash,
- abi: @implementation_abi,
- contract_code_md5: "123"
- )
-
- implementation_contract_address_hash_string =
- Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
-
- expect(
- EthereumJSONRPC.Mox,
- :json_rpc,
- fn %{
- id: _id,
- method: "eth_getStorageAt",
- params: [
- _,
- "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
- "latest"
- ]
- },
- _options ->
- {:ok, "0x000000000000000000000000" <> implementation_contract_address_hash_string}
- end
- )
-
- implementation_abi = Chain.get_implementation_abi_from_proxy(smart_contract, [])
-
- assert implementation_abi == @implementation_abi
- end
-
- test "get_implementation_abi/1 returns empty [] abi if implementation address is null" do
- assert Chain.get_implementation_abi(nil) == []
- end
-
- test "get_implementation_abi/1 returns [] if implementation is not verified" do
- implementation_contract_address = insert(:contract_address)
-
- implementation_contract_address_hash_string =
- Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
-
- assert Chain.get_implementation_abi("0x" <> implementation_contract_address_hash_string) == []
- end
-
- test "get_implementation_abi/1 returns implementation abi if implementation is verified" do
- proxy_contract_address = insert(:contract_address)
- insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
-
- implementation_contract_address = insert(:contract_address)
-
- insert(:smart_contract,
- address_hash: implementation_contract_address.hash,
- abi: @implementation_abi,
- contract_code_md5: "123"
- )
-
- implementation_contract_address_hash_string =
- Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
-
- implementation_abi = Chain.get_implementation_abi("0x" <> implementation_contract_address_hash_string)
-
- assert implementation_abi == @implementation_abi
- end
- end
-
- def get_eip1967_implementation do
- EthereumJSONRPC.Mox
- |> expect(:json_rpc, fn %{
- id: 0,
- method: "eth_getStorageAt",
- params: [
- _,
- "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
- "latest"
- ]
- },
- _options ->
- {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
- end)
- |> expect(:json_rpc, fn %{
- id: 0,
- method: "eth_getStorageAt",
- params: [
- _,
- "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50",
- "latest"
- ]
- },
- _options ->
- {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
- end)
- |> expect(:json_rpc, fn %{
- id: 0,
- method: "eth_getStorageAt",
- params: [
- _,
- "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3",
- "latest"
- ]
- },
- _options ->
- {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
- end)
- end
end
diff --git a/apps/explorer/test/explorer/counters/addresses_tokens_usd_sum_counter_test.exs b/apps/explorer/test/explorer/counters/addresses_tokens_usd_sum_counter_test.exs
index 12e7153a0a52..d56753852f02 100644
--- a/apps/explorer/test/explorer/counters/addresses_tokens_usd_sum_counter_test.exs
+++ b/apps/explorer/test/explorer/counters/addresses_tokens_usd_sum_counter_test.exs
@@ -19,15 +19,15 @@ defmodule Explorer.Counters.AddressTokenUsdSumTest do
)
AddressTokenUsdSum.fetch(address.hash, [
- {address_current_token_balance, address_current_token_balance.token},
- {address_current_token_balance_2, address_current_token_balance_2.token}
+ address_current_token_balance,
+ address_current_token_balance_2
])
Process.sleep(200)
assert AddressTokenUsdSum.fetch(address.hash, [
- {address_current_token_balance, address_current_token_balance.token},
- {address_current_token_balance_2, address_current_token_balance_2.token}
+ address_current_token_balance,
+ address_current_token_balance_2
]) ==
Decimal.new(2_010_000)
end
diff --git a/apps/explorer/test/explorer/counters/average_block_time_test.exs b/apps/explorer/test/explorer/counters/average_block_time_test.exs
index d9e0cfb35cd4..3472276d1959 100644
--- a/apps/explorer/test/explorer/counters/average_block_time_test.exs
+++ b/apps/explorer/test/explorer/counters/average_block_time_test.exs
@@ -129,5 +129,55 @@ defmodule Explorer.Counters.AverageBlockTimeTest do
assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT3S")
end
+
+ test "timestamps are compared correctly" do
+ block_number = 99_999_999
+
+ first_timestamp = ~U[2023-08-23 19:04:59.000000Z]
+ pseudo_after_timestamp = ~U[2022-08-23 19:05:59.000000Z]
+
+ insert(:block, number: block_number, consensus: true, timestamp: pseudo_after_timestamp)
+ insert(:block, number: block_number + 1, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 3))
+ insert(:block, number: block_number + 2, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 6))
+
+ Enum.each(1..100, fn i ->
+ insert(:block,
+ number: block_number + i + 2,
+ consensus: true,
+ timestamp: Timex.shift(first_timestamp, seconds: -(101 - i) - 9)
+ )
+ end)
+
+ AverageBlockTime.refresh()
+
+ %{timestamps: timestamps} = :sys.get_state(AverageBlockTime)
+
+ assert Enum.sort_by(timestamps, fn {_bn, ts} -> ts end, &>=/2) == timestamps
+ end
+
+ test "average time are calculated correctly for blocks that are not in chronological order" do
+ block_number = 99_999_999
+
+ first_timestamp = Timex.now()
+
+ insert(:block, number: block_number, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 3))
+ insert(:block, number: block_number + 1, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 6))
+ insert(:block, number: block_number + 2, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 9))
+ insert(:block, number: block_number + 3, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: -69))
+ insert(:block, number: block_number + 4, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: -66))
+ insert(:block, number: block_number + 5, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: -63))
+
+ Enum.each(1..100, fn i ->
+ insert(:block,
+ number: block_number + i + 5,
+ consensus: true,
+ timestamp: Timex.shift(first_timestamp, seconds: -(101 - i) - 9)
+ )
+ end)
+
+ AverageBlockTime.refresh()
+
+ assert Timex.Duration.to_milliseconds(AverageBlockTime.average_block_time()) == 3000
+ end
end
end
diff --git a/apps/explorer/test/explorer/etherscan/logs_test.exs b/apps/explorer/test/explorer/etherscan/logs_test.exs
index 490dce199dc6..b5953407f83c 100644
--- a/apps/explorer/test/explorer/etherscan/logs_test.exs
+++ b/apps/explorer/test/explorer/etherscan/logs_test.exs
@@ -6,6 +6,25 @@ defmodule Explorer.Etherscan.LogsTest do
alias Explorer.Etherscan.Logs
alias Explorer.Chain.Transaction
+ @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65"
+ @first_topic_hex_string_2 "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
+ @first_topic_hex_string_3 "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"
+
+ @second_topic_hex_string_1 "0x00000000000000000000000098a9dc37d3650b5b30d6c12789b3881ee0b70c16"
+ @second_topic_hex_string_2 "0x000000000000000000000000e2680fd7cdbb04e9087a647ad4d023ef6c8fb4e2"
+ @second_topic_hex_string_3 "0x0000000000000000000000005777d92f208679db4b9778590fa3cab3ac9e2168"
+
+ @third_topic_hex_string_1 "0x0000000000000000000000005079fc00f00f30000e0c8c083801cfde000008b6"
+ @third_topic_hex_string_2 "0x000000000000000000000000e2680fd7cdbb04e9087a647ad4d023ef6c8fb4e2"
+ @third_topic_hex_string_3 "0x0000000000000000000000000f6d9bd6fc315bbf95b5c44f4eba2b2762f8c372"
+
+ @fourth_topic_hex_string_1 "0x8c9b7729443a4444242342b2ca385a239a5c1d76a88473e1cd2ab0c70dd1b9c7"
+
+ defp topic(topic_hex_string) do
+ {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string)
+ topic
+ end
+
describe "list_logs/1" do
test "with empty db" do
contract_address = build(:contract_address)
@@ -38,14 +57,15 @@ defmodule Explorer.Etherscan.LogsTest do
test "with address with one log response includes all required information" do
contract_address = insert(:contract_address)
+ block = insert(:block)
transaction =
- %Transaction{block: block} =
+ %Transaction{} =
:transaction
- |> insert(to_address: contract_address)
- |> with_block()
+ |> insert(to_address: contract_address, block_timestamp: block.timestamp)
+ |> with_block(block)
- log = insert(:log, address: contract_address, transaction: transaction)
+ log = insert(:log, address: contract_address, block_number: block.number, transaction: transaction)
filter = %{
from_block: block.number,
@@ -79,7 +99,7 @@ defmodule Explorer.Etherscan.LogsTest do
|> insert(to_address: contract_address)
|> with_block()
- insert_list(2, :log, address: contract_address, transaction: transaction)
+ insert_list(2, :log, address: contract_address, transaction: transaction, block_number: block.number)
filter = %{
from_block: block.number,
@@ -110,8 +130,8 @@ defmodule Explorer.Etherscan.LogsTest do
|> insert(to_address: contract_address)
|> with_block(second_block)
- insert(:log, address: contract_address, transaction: transaction_block1)
- insert(:log, address: contract_address, transaction: transaction_block2)
+ insert(:log, address: contract_address, transaction: transaction_block1, block_number: first_block.number)
+ insert(:log, address: contract_address, transaction: transaction_block2, block_number: second_block.number)
filter = %{
from_block: second_block.number,
@@ -143,8 +163,8 @@ defmodule Explorer.Etherscan.LogsTest do
|> insert(to_address: contract_address)
|> with_block(second_block)
- insert(:log, address: contract_address, transaction: transaction_block1)
- insert(:log, address: contract_address, transaction: transaction_block2)
+ insert(:log, address: contract_address, transaction: transaction_block1, block_number: first_block.number)
+ insert(:log, address: contract_address, transaction: transaction_block2, block_number: second_block.number)
filter = %{
from_block: first_block.number,
@@ -167,7 +187,8 @@ defmodule Explorer.Etherscan.LogsTest do
|> insert(to_address: contract_address)
|> with_block()
- inserted_records = insert_list(2000, :log, address: contract_address, transaction: transaction)
+ inserted_records =
+ insert_list(2000, :log, address: contract_address, transaction: transaction, block_number: block.number)
filter = %{
from_block: block.number,
@@ -183,7 +204,6 @@ defmodule Explorer.Etherscan.LogsTest do
next_page_params = %{
log_index: last_record.index,
- transaction_index: last_record.transaction_index,
block_number: transaction.block_number
}
@@ -210,13 +230,13 @@ defmodule Explorer.Etherscan.LogsTest do
log1_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some topic"
+ first_topic: topic(@first_topic_hex_string_1)
]
log2_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some other topic"
+ first_topic: topic(@first_topic_hex_string_2)
]
log1 = insert(:log, log1_details)
@@ -246,15 +266,15 @@ defmodule Explorer.Etherscan.LogsTest do
log1_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some first topic",
- second_topic: "some second topic"
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_1)
]
log2_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some first topic",
- second_topic: "some OTHER second topic"
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_2)
]
_log1 = insert(:log, log1_details)
@@ -287,15 +307,15 @@ defmodule Explorer.Etherscan.LogsTest do
log1_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some first topic",
- second_topic: "some second topic"
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_1)
]
log2_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some OTHER first topic",
- second_topic: "some OTHER second topic"
+ first_topic: topic(@first_topic_hex_string_2),
+ second_topic: topic(@second_topic_hex_string_2)
]
log1 = insert(:log, log1_details)
@@ -326,13 +346,15 @@ defmodule Explorer.Etherscan.LogsTest do
log1_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some first topic"
+ first_topic: topic(@first_topic_hex_string_1),
+ block_number: block.number
]
log2_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some OTHER first topic"
+ first_topic: topic(@first_topic_hex_string_2),
+ block_number: block.number
]
_log1 = insert(:log, log1_details)
@@ -363,15 +385,17 @@ defmodule Explorer.Etherscan.LogsTest do
log1_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some first topic",
- second_topic: "some second topic"
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_1),
+ block_number: block.number
]
log2_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some OTHER first topic",
- second_topic: "some OTHER second topic"
+ first_topic: topic(@first_topic_hex_string_2),
+ second_topic: topic(@second_topic_hex_string_2),
+ block_number: block.number
]
_log1 = insert(:log, log1_details)
@@ -404,25 +428,28 @@ defmodule Explorer.Etherscan.LogsTest do
log1_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some first topic",
- second_topic: "some second topic",
- third_topic: "some third topic"
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_1),
+ third_topic: topic(@third_topic_hex_string_1),
+ block_number: block.number
]
log2_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some OTHER first topic",
- second_topic: "some OTHER second topic",
- third_topic: "some OTHER third topic"
+ first_topic: topic(@first_topic_hex_string_2),
+ second_topic: topic(@second_topic_hex_string_2),
+ third_topic: topic(@third_topic_hex_string_2),
+ block_number: block.number
]
log3_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some ALT first topic",
- second_topic: "some ALT second topic",
- third_topic: "some ALT third topic"
+ first_topic: topic(@first_topic_hex_string_3),
+ second_topic: topic(@second_topic_hex_string_3),
+ third_topic: topic(@third_topic_hex_string_3),
+ block_number: block.number
]
_log1 = insert(:log, log1_details)
@@ -461,25 +488,28 @@ defmodule Explorer.Etherscan.LogsTest do
log1_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some first topic",
- second_topic: "some second topic",
- third_topic: "some third topic"
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_1),
+ third_topic: topic(@third_topic_hex_string_1),
+ block_number: block.number
]
log2_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some OTHER first topic",
- second_topic: "some OTHER second topic",
- third_topic: "some OTHER third topic"
+ first_topic: topic(@first_topic_hex_string_2),
+ second_topic: topic(@second_topic_hex_string_2),
+ third_topic: topic(@third_topic_hex_string_2),
+ block_number: block.number
]
log3_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some ALT first topic",
- second_topic: "some ALT second topic",
- third_topic: "some ALT third topic"
+ first_topic: topic(@first_topic_hex_string_3),
+ second_topic: topic(@second_topic_hex_string_3),
+ third_topic: topic(@third_topic_hex_string_3),
+ block_number: block.number
]
log1 = insert(:log, log1_details)
@@ -518,25 +548,28 @@ defmodule Explorer.Etherscan.LogsTest do
log1_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some topic",
- second_topic: "some second topic",
- third_topic: "some third topic"
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_1),
+ third_topic: topic(@third_topic_hex_string_1),
+ block_number: block.number
]
log2_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some topic",
- second_topic: "some OTHER second topic",
- third_topic: "some third topic"
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_2),
+ third_topic: topic(@third_topic_hex_string_1),
+ block_number: block.number
]
log3_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some topic",
- second_topic: "some second topic",
- third_topic: "some third topic"
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_1),
+ third_topic: topic(@third_topic_hex_string_1),
+ block_number: block.number
]
log1 = insert(:log, log1_details)
@@ -575,26 +608,29 @@ defmodule Explorer.Etherscan.LogsTest do
log1_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some topic",
- second_topic: "some second topic"
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_1),
+ block_number: block.number
]
log2_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some OTHER topic",
- second_topic: "some OTHER second topic",
- third_topic: "some OTHER third topic",
- fourth_topic: "some fourth topic"
+ first_topic: topic(@first_topic_hex_string_2),
+ second_topic: topic(@second_topic_hex_string_2),
+ third_topic: topic(@third_topic_hex_string_2),
+ fourth_topic: topic(@fourth_topic_hex_string_1),
+ block_number: block.number
]
log3_details = [
address: contract_address,
transaction: transaction,
- first_topic: "some topic",
- second_topic: "some second topic",
- third_topic: "some third topic",
- fourth_topic: "some fourth topic"
+ first_topic: topic(@first_topic_hex_string_1),
+ second_topic: topic(@second_topic_hex_string_1),
+ third_topic: topic(@third_topic_hex_string_1),
+ fourth_topic: topic(@fourth_topic_hex_string_1),
+ block_number: block.number
]
log1 = insert(:log, log1_details)
diff --git a/apps/explorer/test/explorer/etherscan_test.exs b/apps/explorer/test/explorer/etherscan_test.exs
index 02cc0ced53cb..7d188cd98f49 100644
--- a/apps/explorer/test/explorer/etherscan_test.exs
+++ b/apps/explorer/test/explorer/etherscan_test.exs
@@ -159,11 +159,12 @@ defmodule Explorer.EtherscanTest do
test "loads block_timestamp" do
address = insert(:address)
+ block = insert(:block)
- %Transaction{block: block} =
+ %Transaction{} =
:transaction
- |> insert(from_address: address)
- |> with_block()
+ |> insert(from_address: address, block_timestamp: block.timestamp)
+ |> with_block(block)
[found_transaction] = Etherscan.list_transactions(address.hash)
@@ -370,7 +371,7 @@ defmodule Explorer.EtherscanTest do
for block <- Enum.concat([blocks1, blocks2, blocks3]) do
2
- |> insert_list(:transaction, from_address: address)
+ |> insert_list(:transaction, from_address: address, block_timestamp: block.timestamp)
|> with_block(block)
end
@@ -629,7 +630,7 @@ defmodule Explorer.EtherscanTest do
transaction =
:transaction
- |> insert(from_address: address, to_address: nil)
+ |> insert(from_address: address, to_address: nil, block_timestamp: block.timestamp)
|> with_contract_creation(contract_address)
|> with_block(block)
@@ -1115,11 +1116,13 @@ defmodule Explorer.EtherscanTest do
end
test "returns all required fields" do
+ block = insert(:block)
+
transaction =
%{block: block} =
:transaction
- |> insert()
- |> with_block()
+ |> insert(block_timestamp: block.timestamp)
+ |> with_block(block)
token_transfer =
insert(:token_transfer,
diff --git a/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs b/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs
index 3558c6d2a577..25faf397b422 100644
--- a/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs
+++ b/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs
@@ -1,5 +1,5 @@
defmodule Explorer.ExchangeRatesTest do
- use ExUnit.Case, async: false
+ use Explorer.DataCase
import Mox
@@ -7,12 +7,16 @@ defmodule Explorer.ExchangeRatesTest do
alias Explorer.ExchangeRates
alias Explorer.ExchangeRates.Token
alias Explorer.ExchangeRates.Source.TestSource
+ alias Explorer.Market.MarketHistoryCache
@moduletag :capture_log
setup :verify_on_exit!
setup do
+ Supervisor.terminate_child(Explorer.Supervisor, {ConCache, MarketHistoryCache.cache_name()})
+ Supervisor.restart_child(Explorer.Supervisor, {ConCache, MarketHistoryCache.cache_name()})
+
# Use TestSource mock and ets table for this test set
source_configuration = Application.get_env(:explorer, Explorer.ExchangeRates.Source)
rates_configuration = Application.get_env(:explorer, Explorer.ExchangeRates)
@@ -24,6 +28,8 @@ defmodule Explorer.ExchangeRatesTest do
on_exit(fn ->
Application.put_env(:explorer, Explorer.ExchangeRates.Source, source_configuration)
Application.put_env(:explorer, Explorer.ExchangeRates, rates_configuration)
+ Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
+ Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
end)
end
@@ -75,6 +81,7 @@ defmodule Explorer.ExchangeRatesTest do
id: "test_id",
last_updated: DateTime.utc_now(),
market_cap_usd: Decimal.new("1000000.0"),
+ tvl_usd: Decimal.new("2000000.0"),
name: "test_name",
symbol: "test_symbol",
usd_value: Decimal.new("1.0"),
diff --git a/apps/explorer/test/explorer/exchange_rates/source/coin_gecko_test.exs b/apps/explorer/test/explorer/exchange_rates/source/coin_gecko_test.exs
index 9f28e1281494..044c84553f3e 100644
--- a/apps/explorer/test/explorer/exchange_rates/source/coin_gecko_test.exs
+++ b/apps/explorer/test/explorer/exchange_rates/source/coin_gecko_test.exs
@@ -58,6 +58,38 @@ defmodule Explorer.ExchangeRates.Source.CoinGeckoTest do
]
"""
+ describe "source_url/1" do
+ setup do
+ bypass = Bypass.open()
+ Application.put_env(:explorer, CoinGecko, base_url: "https://api.coingecko.com/api/v3")
+
+ {:ok, bypass: bypass}
+ end
+
+ test "composes cg :coins_list URL" do
+ assert "https://api.coingecko.com/api/v3/coins/list?include_platform=true" == CoinGecko.source_url(:coins_list)
+ end
+
+ test "composes cg url to list of contract address hashes" do
+ assert "https://api.coingecko.com/api/v3/simple/token_price/ethereum?vs_currencies=usd&include_market_cap=true&contract_addresses=0xdAC17F958D2ee523a2206206994597C13D831ec7" ==
+ CoinGecko.source_url(["0xdAC17F958D2ee523a2206206994597C13D831ec7"])
+ end
+
+ test "composes cg url by contract address hash" do
+ assert "https://api.coingecko.com/api/v3/coins/ethereum/contract/0xdAC17F958D2ee523a2206206994597C13D831ec7" ==
+ CoinGecko.source_url("0xdAC17F958D2ee523a2206206994597C13D831ec7")
+ end
+
+ test "composes cg url by contract address hash with custom coin_id" do
+ Application.put_env(:explorer, CoinGecko, platform: "poa-network")
+
+ assert "https://api.coingecko.com/api/v3/coins/poa-network/contract/0xdAC17F958D2ee523a2206206994597C13D831ec7" ==
+ CoinGecko.source_url("0xdAC17F958D2ee523a2206206994597C13D831ec7")
+
+ Application.put_env(:explorer, CoinGecko, platform: nil)
+ end
+ end
+
describe "format_data/1" do
setup do
bypass = Bypass.open()
@@ -84,6 +116,7 @@ defmodule Explorer.ExchangeRates.Source.CoinGeckoTest do
id: "poa-network",
last_updated: ~U[2019-08-21 08:36:49.371Z],
market_cap_usd: Decimal.new("2962791"),
+ tvl_usd: nil,
name: "POA Network",
symbol: "POA",
usd_value: Decimal.new("0.01345698"),
@@ -164,25 +197,5 @@ defmodule Explorer.ExchangeRates.Source.CoinGeckoTest do
assert CoinGecko.coin_id() == {:ok, "callisto"}
end
-
- test "returns redirect on fetching", %{bypass: bypass} do
- Application.put_env(:explorer, :coin, "DAI")
-
- Bypass.expect(bypass, "GET", "/coins/list", fn conn ->
- Conn.resp(conn, 302, "Request redirected...")
- end)
-
- assert CoinGecko.coin_id() == {:error, "Source redirected"}
- end
-
- test "returns error on fetching", %{bypass: bypass} do
- Application.put_env(:explorer, :coin, "DAI")
-
- Bypass.expect(bypass, "GET", "/coins/list", fn conn ->
- Conn.resp(conn, 503, "Internal server error...")
- end)
-
- assert CoinGecko.coin_id() == {:error, "Internal server error..."}
- end
end
end
diff --git a/apps/explorer/test/explorer/exchange_rates/source/coin_market_cap_test.exs b/apps/explorer/test/explorer/exchange_rates/source/coin_market_cap_test.exs
new file mode 100644
index 000000000000..1d957f4e4aa0
--- /dev/null
+++ b/apps/explorer/test/explorer/exchange_rates/source/coin_market_cap_test.exs
@@ -0,0 +1,136 @@
+defmodule Explorer.ExchangeRates.Source.CoinMarketCapTest do
+ use ExUnit.Case
+
+ alias Explorer.ExchangeRates.Source.CoinMarketCap
+
+ describe "source_url/0" do
+ test "returns default cmc source url" do
+ assert "https://pro-api.coinmarketcap.com/v2/cryptocurrency/quotes/latest?symbol=ETH&CMC_PRO_API_KEY=" ==
+ CoinMarketCap.source_url()
+ end
+
+ test "returns cmc source url with not default symbol" do
+ Application.put_env(:explorer, :coin, "ETC")
+
+ assert "https://pro-api.coinmarketcap.com/v2/cryptocurrency/quotes/latest?symbol=ETC&CMC_PRO_API_KEY=" ==
+ CoinMarketCap.source_url()
+
+ Application.put_env(:explorer, :coin, "ETH")
+ end
+
+ test "returns cmc source url with id" do
+ Application.put_env(:explorer, CoinMarketCap, coin_id: 100_500)
+
+ assert "https://pro-api.coinmarketcap.com/v2/cryptocurrency/quotes/latest?id=100500&CMC_PRO_API_KEY=" ==
+ CoinMarketCap.source_url()
+
+ Application.put_env(:explorer, CoinMarketCap, coin_id: nil)
+ end
+ end
+
+ describe "source_url/1" do
+ test "returns cmc source url for symbol" do
+ assert "https://pro-api.coinmarketcap.com/v2/cryptocurrency/quotes/latest?symbol=ETH&CMC_PRO_API_KEY=" ==
+ CoinMarketCap.source_url("ETH")
+ end
+ end
+
+ @token_properties %{
+ "circulating_supply" => 0,
+ "cmc_rank" => 2977,
+ "date_added" => "2021-12-06T11:25:31.000Z",
+ "id" => 15658,
+ "infinite_supply" => false,
+ "is_active" => 1,
+ "is_fiat" => 0,
+ "last_updated" => "2023-09-12T09:03:00.000Z",
+ "max_supply" => 210_240_000,
+ "name" => "Qitmeer Network",
+ "num_market_pairs" => 10,
+ "platform" => nil,
+ "quote" => %{
+ "USD" => %{
+ "fully_diluted_market_cap" => 27_390_222.61,
+ "last_updated" => "2023-09-12T09:03:00.000Z",
+ "market_cap" => 0,
+ "market_cap_dominance" => 0,
+ "percent_change_1h" => -0.14807635,
+ "percent_change_24h" => -4.05784287,
+ "percent_change_30d" => -20.18918329,
+ "percent_change_60d" => 85.21384726,
+ "percent_change_7d" => -5.49776979,
+ "percent_change_90d" => 22.27442093,
+ "price" => 0.13028073920022334,
+ "tvl" => nil,
+ "volume_24h" => 93766.09652096,
+ "volume_change_24h" => -0.9423
+ }
+ },
+ "self_reported_circulating_supply" => 71_348_557,
+ "self_reported_market_cap" => 9_295_342.74682927,
+ "slug" => "qitmeer-network",
+ "symbol" => "MEER",
+ "tags" => [],
+ "total_supply" => 71_348_557,
+ "tvl_ratio" => nil
+ }
+
+ @market_data_multiple_tokens %{
+ "MEER" => [
+ @token_properties,
+ %{
+ "circulating_supply" => nil,
+ "cmc_rank" => nil,
+ "date_added" => "2023-05-12T15:52:05.000Z",
+ "id" => 25240,
+ "infinite_supply" => false,
+ "is_active" => 0,
+ "is_fiat" => 0,
+ "last_updated" => "2023-09-12T09:05:15.725Z",
+ "max_supply" => 210_240_000,
+ "name" => "Meer Coin",
+ "num_market_pairs" => nil,
+ "platform" => nil,
+ "quote" => %{
+ "USD" => %{
+ "fully_diluted_market_cap" => nil,
+ "last_updated" => "2023-09-12T09:05:15.725Z",
+ "market_cap" => nil,
+ "market_cap_dominance" => nil,
+ "percent_change_1h" => nil,
+ "percent_change_24h" => nil,
+ "percent_change_30d" => nil,
+ "percent_change_60d" => nil,
+ "percent_change_7d" => nil,
+ "percent_change_90d" => nil,
+ "price" => 0,
+ "tvl" => nil,
+ "volume_24h" => nil,
+ "volume_change_24h" => nil
+ }
+ },
+ "self_reported_circulating_supply" => nil,
+ "self_reported_market_cap" => nil,
+ "slug" => "meer-coin",
+ "symbol" => "MEER",
+ "tags" => [],
+ "total_supply" => nil,
+ "tvl_ratio" => nil
+ }
+ ]
+ }
+
+ @market_data_single_token %{
+ "15658" => @token_properties
+ }
+
+ describe "get_token_properties/1" do
+ test "returns a single token property, when market_data contains multiple tokens" do
+ assert CoinMarketCap.get_token_properties(@market_data_multiple_tokens) == @token_properties
+ end
+
+ test "returns a single token property, when market_data contains a single token" do
+ assert CoinMarketCap.get_token_properties(@market_data_single_token) == @token_properties
+ end
+ end
+end
diff --git a/apps/explorer/test/explorer/exchange_rates/token_exchange_rates_test.exs b/apps/explorer/test/explorer/exchange_rates/token_exchange_rates_test.exs
index cda4f83eaecb..2e5f9ee1a6c6 100644
--- a/apps/explorer/test/explorer/exchange_rates/token_exchange_rates_test.exs
+++ b/apps/explorer/test/explorer/exchange_rates/token_exchange_rates_test.exs
@@ -5,7 +5,6 @@ defmodule Explorer.TokenExchangeRatesTest do
alias Plug.Conn
alias Explorer.Chain.Token
- alias Explorer.ExchangeRates
alias Explorer.ExchangeRates.TokenExchangeRates
@moduletag :capture_log
@@ -35,11 +34,32 @@ defmodule Explorer.TokenExchangeRatesTest do
base_url: "http://localhost:#{bypass.port}"
)
- tokens =
+ [_token_with_no_exchange_rate | tokens] =
for _ <- 0..4 do
- insert(:token)
+ insert(:token, fiat_value: nil)
end
+ coins_list =
+ tokens
+ |> Enum.map(fn %{contract_address_hash: contract_address_hash} ->
+ contract_address_hash_str = to_string(contract_address_hash)
+
+ %{
+ "id" => "#{contract_address_hash_str}_id",
+ "symbol" => "#{contract_address_hash_str}_symbol",
+ "name" => "#{contract_address_hash_str}_name",
+ "platforms" => %{
+ "some_other_chain" => "we do not want this to appear in the /simple/token_price/ request",
+ "ethereum" => contract_address_hash_str
+ }
+ }
+ end)
+
+ Bypass.expect_once(bypass, "GET", "/coins/list", fn conn ->
+ assert conn.query_string == "include_platform=true"
+ Conn.resp(conn, 200, Jason.encode!(coins_list))
+ end)
+
token_exchange_rates =
tokens
|> Enum.reduce(%{}, fn %{contract_address_hash: contract_address_hash}, acc ->
@@ -52,19 +72,18 @@ defmodule Explorer.TokenExchangeRatesTest do
tokens
|> Enum.map_join(",", fn %{contract_address_hash: contract_address_hash} -> to_string(contract_address_hash) end)
- Bypass.expect_once(bypass, fn conn ->
- assert conn.method == "GET"
-
- assert "#{conn.request_path}?#{conn.query_string}" ==
- "/simple/token_price/ethereum?vs_currencies=usd&include_market_cap=true&contract_addresses=#{joined_addresses}"
-
- Conn.resp(conn, 200, Jason.encode!(token_exchange_rates))
- end)
+ Bypass.expect_once(
+ bypass,
+ "GET",
+ "/simple/token_price/ethereum",
+ fn conn ->
+ assert conn.query_string == "vs_currencies=usd&include_market_cap=true&contract_addresses=#{joined_addresses}"
+ Conn.resp(conn, 200, Jason.encode!(token_exchange_rates))
+ end
+ )
GenServer.start_link(TokenExchangeRates, [])
- set_mox_global()
-
:timer.sleep(100)
Repo.all(Token)
@@ -73,6 +92,31 @@ defmodule Explorer.TokenExchangeRatesTest do
end)
end
+ test "empty body in /coins/list response" do
+ bypass = Bypass.open()
+
+ Application.put_env(:explorer, Explorer.ExchangeRates.Source.CoinGecko,
+ base_url: "http://localhost:#{bypass.port}"
+ )
+
+ [_token_with_no_exchange_rate | _tokens] =
+ for _ <- 0..4 do
+ insert(:token, fiat_value: nil)
+ end
+
+ Bypass.expect_once(bypass, "GET", "/coins/list", fn conn ->
+ assert conn.query_string == "include_platform=true"
+ Conn.resp(conn, 200, "[]")
+ end)
+
+ GenServer.start_link(TokenExchangeRates, [])
+
+ :timer.sleep(100)
+
+ Repo.all(Token)
+ |> Enum.each(fn %{fiat_value: fiat_value} -> assert is_nil(fiat_value) end)
+ end
+
test "empty body in fetch response" do
bypass = Bypass.open()
@@ -80,63 +124,128 @@ defmodule Explorer.TokenExchangeRatesTest do
base_url: "http://localhost:#{bypass.port}"
)
- tokens =
+ [_token_with_no_exchange_rate | tokens] =
for _ <- 0..4 do
- insert(:token)
+ insert(:token, fiat_value: nil)
end
+ coins_list =
+ tokens
+ |> Enum.map(fn %{contract_address_hash: contract_address_hash} ->
+ contract_address_hash_str = to_string(contract_address_hash)
+
+ %{
+ "id" => "#{contract_address_hash_str}_id",
+ "symbol" => "#{contract_address_hash_str}_symbol",
+ "name" => "#{contract_address_hash_str}_name",
+ "platforms" => %{
+ "some_other_chain" => "we do not want this to appear in the /simple/token_price/ request",
+ "ethereum" => contract_address_hash_str
+ }
+ }
+ end)
+
+ Bypass.expect_once(bypass, "GET", "/coins/list", fn conn ->
+ assert conn.query_string == "include_platform=true"
+ Conn.resp(conn, 200, Jason.encode!(coins_list))
+ end)
+
joined_addresses =
tokens
|> Enum.map_join(",", fn %{contract_address_hash: contract_address_hash} -> to_string(contract_address_hash) end)
- Bypass.expect_once(bypass, fn conn ->
- assert conn.method == "GET"
+ Bypass.expect_once(
+ bypass,
+ "GET",
+ "/simple/token_price/ethereum",
+ fn conn ->
+ assert conn.query_string == "vs_currencies=usd&include_market_cap=true&contract_addresses=#{joined_addresses}"
+ Conn.resp(conn, 200, "{}")
+ end
+ )
+
+ GenServer.start_link(TokenExchangeRates, [])
+
+ :timer.sleep(100)
+
+ Repo.all(Token)
+ |> Enum.each(fn %{fiat_value: fiat_value} -> assert is_nil(fiat_value) end)
+ end
+
+ test "error in /coins/list response" do
+ bypass = Bypass.open()
+
+ Application.put_env(:explorer, Explorer.ExchangeRates.Source.CoinGecko,
+ base_url: "http://localhost:#{bypass.port}"
+ )
- assert "#{conn.request_path}?#{conn.query_string}" ==
- "/simple/token_price/ethereum?vs_currencies=usd&include_market_cap=true&contract_addresses=#{joined_addresses}"
+ [_token_with_no_exchange_rate | _tokens] =
+ for _ <- 0..4 do
+ insert(:token, fiat_value: nil)
+ end
- Conn.resp(conn, 200, "{}")
+ Bypass.expect_once(bypass, "GET", "/coins/list", fn conn ->
+ assert conn.query_string == "include_platform=true"
+ Conn.resp(conn, 429, "Too many requests")
end)
GenServer.start_link(TokenExchangeRates, [])
- set_mox_global()
-
:timer.sleep(100)
Repo.all(Token)
|> Enum.each(fn %{fiat_value: fiat_value} -> assert is_nil(fiat_value) end)
end
- test "error in retch response" do
+ test "error in fetch response" do
bypass = Bypass.open()
Application.put_env(:explorer, Explorer.ExchangeRates.Source.CoinGecko,
base_url: "http://localhost:#{bypass.port}"
)
- tokens =
+ [_token_with_no_exchange_rate | tokens] =
for _ <- 0..4 do
- insert(:token)
+ insert(:token, fiat_value: nil)
end
- joined_addresses =
+ coins_list =
tokens
- |> Enum.map_join(",", fn %{contract_address_hash: contract_address_hash} -> to_string(contract_address_hash) end)
+ |> Enum.map(fn %{contract_address_hash: contract_address_hash} ->
+ contract_address_hash_str = to_string(contract_address_hash)
+
+ %{
+ "id" => "#{contract_address_hash_str}_id",
+ "symbol" => "#{contract_address_hash_str}_symbol",
+ "name" => "#{contract_address_hash_str}_name",
+ "platforms" => %{
+ "some_other_chain" => "we do not want this to appear in the /simple/token_price/ request",
+ "ethereum" => contract_address_hash_str
+ }
+ }
+ end)
- Bypass.expect_once(bypass, fn conn ->
- assert conn.method == "GET"
+ Bypass.expect_once(bypass, "GET", "/coins/list", fn conn ->
+ assert conn.query_string == "include_platform=true"
+ Conn.resp(conn, 200, Jason.encode!(coins_list))
+ end)
- assert "#{conn.request_path}?#{conn.query_string}" ==
- "/simple/token_price/ethereum?vs_currencies=usd&include_market_cap=true&contract_addresses=#{joined_addresses}"
+ joined_addresses =
+ tokens
+ |> Enum.map_join(",", fn %{contract_address_hash: contract_address_hash} -> to_string(contract_address_hash) end)
- Conn.resp(conn, 429, "Too Many Requests")
- end)
+ Bypass.expect_once(
+ bypass,
+ "GET",
+ "/simple/token_price/ethereum",
+ fn conn ->
+ assert conn.query_string == "vs_currencies=usd&include_market_cap=true&contract_addresses=#{joined_addresses}"
+ Conn.resp(conn, 429, "Too many requests")
+ end
+ )
GenServer.start_link(TokenExchangeRates, [])
- set_mox_global()
-
:timer.sleep(100)
Repo.all(Token)
diff --git a/apps/explorer/test/explorer/market/history/cataloger_test.exs b/apps/explorer/test/explorer/market/history/cataloger_test.exs
index 433ad0fc1e5c..f7a86a960e2c 100644
--- a/apps/explorer/test/explorer/market/history/cataloger_test.exs
+++ b/apps/explorer/test/explorer/market/history/cataloger_test.exs
@@ -5,37 +5,158 @@ defmodule Explorer.Market.History.CatalogerTest do
alias Explorer.Market.MarketHistory
alias Explorer.Market.History.Cataloger
- alias Explorer.Market.History.Source.TestSource
+ alias Explorer.Market.History.Source.Price.TestSource
+ alias Explorer.Market.History.Source.Price.CryptoCompare
alias Explorer.Repo
+ alias Plug.Conn
setup do
Application.put_env(:explorer, Cataloger, source: TestSource)
+ Application.put_env(:explorer, Cataloger, enabled: true)
:ok
end
test "init" do
assert {:ok, %{}} == Cataloger.init(:ok)
- assert_received {:fetch_history, 365}
+ assert_received {:fetch_price_history, 365}
end
- test "handle_info with `{:fetch_history, days}`" do
+ test "handle_info with `{:fetch_price_history, days}`" do
+ bypass = Bypass.open()
+ Application.put_env(:explorer, CryptoCompare, base_url: "http://localhost:#{bypass.port}")
+
+ resp = """
+ {
+ "Response": "Success",
+ "Type": 100,
+ "Aggregated": false,
+ "TimeTo": 1522569618,
+ "TimeFrom": 1522566018,
+ "FirstValueInArray": true,
+ "ConversionType": {
+ "type": "multiply",
+ "conversionSymbol": "ETH"
+ },
+ "Data": [{
+ "time": 1522566018,
+ "high": 10,
+ "low": 5,
+ "open": 5,
+ "volumefrom": 0,
+ "volumeto": 0,
+ "close": 10,
+ "conversionType": "multiply",
+ "conversionSymbol": "ETH"
+ }],
+ "RateLimit": {},
+ "HasWarning": false
+ }
+ """
+
+ Bypass.expect(bypass, fn conn -> Conn.resp(conn, 200, resp) end)
records = [%{date: ~D[2018-04-01], closing_price: Decimal.new(10), opening_price: Decimal.new(5)}]
- expect(TestSource, :fetch_history, fn 1 -> {:ok, records} end)
+ expect(TestSource, :fetch_price_history, fn 1 -> {:ok, records} end)
set_mox_global()
state = %{}
- assert {:noreply, state} == Cataloger.handle_info({:fetch_history, 1}, state)
- assert_receive {_ref, {1, 0, {:ok, ^records}}}
+ assert {:noreply, state} == Cataloger.handle_info({:fetch_price_history, 1}, state)
+ assert_receive {_ref, {:price_history, {1, 0, {:ok, ^records}}}}
end
- test "handle_info with successful task" do
+ test "handle_info with successful tasks (price, market cap and tvl)" do
Application.put_env(:explorer, Cataloger, history_fetch_interval: 1)
- record = %{date: ~D[2018-04-01], closing_price: Decimal.new(10), opening_price: Decimal.new(5)}
- state = %{}
- assert {:noreply, state} == Cataloger.handle_info({nil, {1, 0, {:ok, [record]}}}, state)
- assert_receive {:fetch_history, 1}
- assert Repo.get_by(MarketHistory, date: record.date)
+ price_records = [
+ %{date: ~D[2018-04-01], closing_price: Decimal.new(10), opening_price: Decimal.new(5)},
+ %{date: ~D[2018-04-02], closing_price: Decimal.new(6), opening_price: Decimal.new(2)}
+ ]
+
+ market_cap_records = [%{date: ~D[2018-04-01], market_cap: Decimal.new(100_500)}]
+ tvl_records = [%{date: ~D[2018-04-01], tvl: Decimal.new(200_500)}]
+
+ state = %{
+ price_records: price_records
+ }
+
+ state2 = Map.put(state, :market_cap_records, market_cap_records)
+
+ state3 = Map.put(state2, :tvl_records, tvl_records)
+
+ assert {:noreply, state} == Cataloger.handle_info({nil, {:price_history, {1, 0, {:ok, price_records}}}}, state)
+ assert_receive {:fetch_market_cap_history, 365}
+
+ assert {:noreply, state2} ==
+ Cataloger.handle_info({nil, {:market_cap_history, {0, 3, {:ok, market_cap_records}}}}, state)
+
+ assert {:noreply, state3} ==
+ Cataloger.handle_info({nil, {:tvl_history, {0, 3, {:ok, tvl_records}}}}, state2)
+
+ assert record2 = Repo.get_by(MarketHistory, date: Enum.at(price_records, 1).date)
+ assert record1 = Repo.get_by(MarketHistory, date: Enum.at(price_records, 0).date)
+ assert record2.closing_price == Decimal.new(6)
+ assert record2.market_cap == nil
+ assert record2.tvl == nil
+ assert record1.closing_price == Decimal.new(10)
+ assert record1.market_cap == Decimal.new(100_500)
+ assert record1.tvl == Decimal.new(200_500)
+ end
+
+ test "handle_info with successful tasks (price and market cap)" do
+ Application.put_env(:explorer, Cataloger, history_fetch_interval: 1)
+ price_records = [%{date: ~D[2018-04-01], closing_price: Decimal.new(10), opening_price: Decimal.new(5)}]
+ market_cap_records = [%{date: ~D[2018-04-01], market_cap: Decimal.new(100_500)}]
+ tvl_records = []
+
+ state = %{
+ price_records: price_records
+ }
+
+ state2 = Map.put(state, :market_cap_records, market_cap_records)
+
+ state3 = Map.put(state2, :tvl_records, [])
+
+ assert {:noreply, state} == Cataloger.handle_info({nil, {:price_history, {1, 0, {:ok, price_records}}}}, state)
+ assert_receive {:fetch_market_cap_history, 365}
+
+ assert {:noreply, state2} ==
+ Cataloger.handle_info({nil, {:market_cap_history, {0, 3, {:ok, market_cap_records}}}}, state)
+
+ assert {:noreply, state3} ==
+ Cataloger.handle_info({nil, {:tvl_history, {0, 3, {:ok, tvl_records}}}}, state2)
+
+ assert record = Repo.get_by(MarketHistory, date: Enum.at(price_records, 0).date)
+ assert record.opening_price == Decimal.new(5)
+ assert record.market_cap == Decimal.new(100_500)
+ assert record.tvl == nil
+ end
+
+ test "handle_info with successful price task" do
+ Application.put_env(:explorer, Cataloger, history_fetch_interval: 1)
+ price_records = [%{date: ~D[2018-04-01], closing_price: Decimal.new(10), opening_price: Decimal.new(5)}]
+ market_cap_records = []
+ tvl_records = []
+
+ state = %{
+ price_records: price_records
+ }
+
+ state2 = Map.put(state, :market_cap_records, market_cap_records)
+
+ state3 = Map.put(state2, :tvl_records, tvl_records)
+
+ assert {:noreply, state} == Cataloger.handle_info({nil, {:price_history, {1, 0, {:ok, price_records}}}}, state)
+ assert_receive {:fetch_market_cap_history, 365}
+
+ assert {:noreply, state2} ==
+ Cataloger.handle_info({nil, {:market_cap_history, {0, 3, {:ok, market_cap_records}}}}, state)
+
+ assert {:noreply, state3} ==
+ Cataloger.handle_info({nil, {:tvl_history, {0, 3, {:ok, tvl_records}}}}, state2)
+
+ assert record = Repo.get_by(MarketHistory, date: Enum.at(price_records, 0).date)
+ assert record.closing_price == Decimal.new(10)
+ assert record.market_cap == nil
+ assert record.tvl == nil
end
test "handle info for DOWN message" do
diff --git a/apps/explorer/test/explorer/market/history/source/crypto_compare_test.exs b/apps/explorer/test/explorer/market/history/source/price/crypto_compare_test.exs
similarity index 90%
rename from apps/explorer/test/explorer/market/history/source/crypto_compare_test.exs
rename to apps/explorer/test/explorer/market/history/source/price/crypto_compare_test.exs
index 1fc4223f7262..2d8311b0ae1f 100644
--- a/apps/explorer/test/explorer/market/history/source/crypto_compare_test.exs
+++ b/apps/explorer/test/explorer/market/history/source/price/crypto_compare_test.exs
@@ -1,7 +1,7 @@
-defmodule Explorer.Market.History.Source.CryptoCompareTest do
+defmodule Explorer.Market.History.Source.Price.CryptoCompareTest do
use ExUnit.Case, async: false
- alias Explorer.Market.History.Source.CryptoCompare
+ alias Explorer.Market.History.Source.Price.CryptoCompare
alias Plug.Conn
@json """
@@ -48,7 +48,7 @@ defmodule Explorer.Market.History.Source.CryptoCompareTest do
}
"""
- describe "fetch_history/1" do
+ describe "fetch_price_history/1" do
setup do
bypass = Bypass.open()
Application.put_env(:explorer, CryptoCompare, base_url: "http://localhost:#{bypass.port}")
@@ -77,14 +77,14 @@ defmodule Explorer.Market.History.Source.CryptoCompareTest do
}
]
- assert {:ok, expected} == CryptoCompare.fetch_history(3)
+ assert {:ok, expected} == CryptoCompare.fetch_price_history(3)
end
test "with errored request", %{bypass: bypass} do
error_text = ~S({"error": "server error"})
Bypass.expect(bypass, fn conn -> Conn.resp(conn, 500, error_text) end)
- assert :error == CryptoCompare.fetch_history(3)
+ assert :error == CryptoCompare.fetch_price_history(3)
end
test "rejects empty prices", %{bypass: bypass} do
@@ -138,7 +138,7 @@ defmodule Explorer.Market.History.Source.CryptoCompareTest do
%{closing_price: Decimal.from_float(8804.32), date: ~D[2018-04-26], opening_price: Decimal.from_float(8873.57)}
]
- assert {:ok, expected} == CryptoCompare.fetch_history(3)
+ assert {:ok, expected} == CryptoCompare.fetch_price_history(3)
end
end
end
diff --git a/apps/explorer/test/explorer/migrator/address_current_token_balance_token_type_test.exs b/apps/explorer/test/explorer/migrator/address_current_token_balance_token_type_test.exs
new file mode 100644
index 000000000000..27591d9c52c1
--- /dev/null
+++ b/apps/explorer/test/explorer/migrator/address_current_token_balance_token_type_test.exs
@@ -0,0 +1,33 @@
+defmodule Explorer.Migrator.AddressCurrentTokenBalanceTokenTypeTest do
+ use Explorer.DataCase, async: false
+
+ alias Explorer.Chain.Cache.BackgroundMigrations
+ alias Explorer.Chain.Address.CurrentTokenBalance
+ alias Explorer.Migrator.{AddressCurrentTokenBalanceTokenType, MigrationStatus}
+ alias Explorer.Repo
+
+ describe "Migrate current token balances" do
+ test "Set token_type for not processed current token balances" do
+ Enum.each(0..10, fn _x ->
+ current_token_balance = insert(:address_current_token_balance, token_type: nil)
+ assert %{token_type: nil} = current_token_balance
+ end)
+
+ assert MigrationStatus.get_status("ctb_token_type") == nil
+
+ AddressCurrentTokenBalanceTokenType.start_link([])
+ Process.sleep(100)
+
+ CurrentTokenBalance
+ |> Repo.all()
+ |> Repo.preload(:token)
+ |> Enum.each(fn ctb ->
+ assert %{token_type: token_type, token: %{type: token_type}} = ctb
+ assert not is_nil(token_type)
+ end)
+
+ assert MigrationStatus.get_status("ctb_token_type") == "completed"
+ assert BackgroundMigrations.get_ctb_token_type_finished() == true
+ end
+ end
+end
diff --git a/apps/explorer/test/explorer/migrator/address_token_balance_token_type_test.exs b/apps/explorer/test/explorer/migrator/address_token_balance_token_type_test.exs
new file mode 100644
index 000000000000..4bf09c57122c
--- /dev/null
+++ b/apps/explorer/test/explorer/migrator/address_token_balance_token_type_test.exs
@@ -0,0 +1,33 @@
+defmodule Explorer.Migrator.AddressTokenBalanceTokenTypeTest do
+ use Explorer.DataCase, async: false
+
+ alias Explorer.Chain.Cache.BackgroundMigrations
+ alias Explorer.Chain.Address.TokenBalance
+ alias Explorer.Migrator.{AddressTokenBalanceTokenType, MigrationStatus}
+ alias Explorer.Repo
+
+ describe "Migrate token balances" do
+ test "Set token_type for not processed token balances" do
+ Enum.each(0..10, fn _x ->
+ token_balance = insert(:token_balance, token_type: nil)
+ assert %{token_type: nil} = token_balance
+ end)
+
+ assert MigrationStatus.get_status("tb_token_type") == nil
+
+ AddressTokenBalanceTokenType.start_link([])
+ Process.sleep(100)
+
+ TokenBalance
+ |> Repo.all()
+ |> Repo.preload(:token)
+ |> Enum.each(fn tb ->
+ assert %{token_type: token_type, token: %{type: token_type}} = tb
+ assert not is_nil(token_type)
+ end)
+
+ assert MigrationStatus.get_status("tb_token_type") == "completed"
+ assert BackgroundMigrations.get_tb_token_type_finished() == true
+ end
+ end
+end
diff --git a/apps/explorer/test/explorer/migrator/transactions_denormalization_migrator_test.exs b/apps/explorer/test/explorer/migrator/transactions_denormalization_migrator_test.exs
new file mode 100644
index 000000000000..e47afe96da4d
--- /dev/null
+++ b/apps/explorer/test/explorer/migrator/transactions_denormalization_migrator_test.exs
@@ -0,0 +1,43 @@
+defmodule Explorer.Migrator.TransactionsDenormalizationTest do
+ use Explorer.DataCase, async: false
+
+ alias Explorer.Chain.Cache.BackgroundMigrations
+ alias Explorer.Chain.Transaction
+ alias Explorer.Migrator.{MigrationStatus, TransactionsDenormalization}
+ alias Explorer.Repo
+
+ describe "Migrate transactions" do
+ test "Set block_consensus and block_timestamp for not processed transactions" do
+ Enum.each(0..10, fn _x ->
+ transaction =
+ :transaction
+ |> insert()
+ |> with_block(block_timestamp: nil, block_consensus: nil)
+
+ assert %{block_consensus: nil, block_timestamp: nil, block: %{consensus: consensus, timestamp: timestamp}} =
+ transaction
+
+ assert not is_nil(consensus)
+ assert not is_nil(timestamp)
+ end)
+
+ assert MigrationStatus.get_status("denormalization") == nil
+
+ TransactionsDenormalization.start_link([])
+ Process.sleep(100)
+
+ Transaction
+ |> Repo.all()
+ |> Repo.preload(:block)
+ |> Enum.each(fn t ->
+ assert %{
+ block_consensus: consensus,
+ block_timestamp: timestamp,
+ block: %{consensus: consensus, timestamp: timestamp}
+ } = t
+ end)
+
+ assert MigrationStatus.get_status("denormalization") == "completed"
+ end
+ end
+end
diff --git a/apps/explorer/test/explorer/repo/config_helper_test.exs b/apps/explorer/test/explorer/repo/config_helper_test.exs
index 2b5a32b8dab8..71bb85077e31 100644
--- a/apps/explorer/test/explorer/repo/config_helper_test.exs
+++ b/apps/explorer/test/explorer/repo/config_helper_test.exs
@@ -40,13 +40,25 @@ defmodule Explorer.Repo.ConfigHelperTest do
assert result[:database] == "test-database"
end
+ test "parse params from database url with hyphen in database user name" do
+ database_url = "postgresql://test-username:password@hostname.test.com:7777/database"
+
+ result = ConfigHelper.get_db_config(%{url: database_url, env_func: fn _ -> nil end})
+
+ assert result[:username] == "test-username"
+ assert result[:password] == "password"
+ assert result[:hostname] == "hostname.test.com"
+ assert result[:port] == "7777"
+ assert result[:database] == "database"
+ end
+
test "parse params from database url with special characters in password" do
- database_url = "postgresql://test_username:awN!l#W*g$P%t-l^q&d@hostname.test.com:7777/test_database"
+ database_url = "postgresql://test_username:awN!l#W*g$P%t-l^.q&d@hostname.test.com:7777/test_database"
result = ConfigHelper.get_db_config(%{url: database_url, env_func: fn _ -> nil end})
assert result[:username] == "test_username"
- assert result[:password] == "awN!l#W*g$P%t-l^q&d"
+ assert result[:password] == "awN!l#W*g$P%t-l^.q&d"
assert result[:hostname] == "hostname.test.com"
assert result[:port] == "7777"
assert result[:database] == "test_database"
diff --git a/apps/explorer/test/explorer/smart_contract/helper_test.exs b/apps/explorer/test/explorer/smart_contract/helper_test.exs
index 4cd3832434c9..202c0b7edc1f 100644
--- a/apps/explorer/test/explorer/smart_contract/helper_test.exs
+++ b/apps/explorer/test/explorer/smart_contract/helper_test.exs
@@ -124,4 +124,30 @@ defmodule Explorer.SmartContract.HelperTest do
refute Helper.nonpayable?(function)
end
end
+
+ describe "read_with_wallet_method?" do
+ test "returns payable method with output in the read tab" do
+ function = %{
+ "type" => "function",
+ "stateMutability" => "payable",
+ "outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}],
+ "name" => "returnaddress",
+ "inputs" => []
+ }
+
+ assert Helper.read_with_wallet_method?(function)
+ end
+
+ test "doesn't return payable method with no output in the read tab" do
+ function = %{
+ "type" => "function",
+ "stateMutability" => "payable",
+ "outputs" => [],
+ "name" => "returnaddress",
+ "inputs" => []
+ }
+
+ refute Helper.read_with_wallet_method?(function)
+ end
+ end
end
diff --git a/apps/explorer/test/explorer/smart_contract/reader_test.exs b/apps/explorer/test/explorer/smart_contract/reader_test.exs
index df6ed4727b3b..8eb6e1a2ec6c 100644
--- a/apps/explorer/test/explorer/smart_contract/reader_test.exs
+++ b/apps/explorer/test/explorer/smart_contract/reader_test.exs
@@ -153,7 +153,7 @@ defmodule Explorer.SmartContract.ReaderTest do
"constant" => true,
"inputs" => [],
"name" => "get",
- "outputs" => [%{"name" => "", "type" => "uint256", "value" => 0}],
+ "outputs" => [%{"type" => "uint256", "value" => 0}],
"payable" => _,
"stateMutability" => _,
"type" => _
@@ -162,7 +162,7 @@ defmodule Explorer.SmartContract.ReaderTest do
"constant" => true,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "with_arguments",
- "outputs" => [%{"name" => "", "type" => "bool"}],
+ "outputs" => [%{"type" => "bool"}],
"payable" => _,
"stateMutability" => _,
"type" => _
@@ -238,7 +238,7 @@ defmodule Explorer.SmartContract.ReaderTest do
"constant" => true,
"inputs" => [],
"name" => "get",
- "outputs" => [%{"name" => "", "type" => "uint256", "value" => 0}],
+ "outputs" => [%{"type" => "uint256", "value" => 0}],
"payable" => _,
"stateMutability" => _,
"type" => _
@@ -247,7 +247,7 @@ defmodule Explorer.SmartContract.ReaderTest do
"constant" => true,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "with_arguments",
- "outputs" => [%{"name" => "", "type" => "bool"}],
+ "outputs" => [%{"type" => "bool"}],
"payable" => _,
"stateMutability" => _,
"type" => _
diff --git a/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs b/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs
index 952c320ebf7e..ed80cb12191d 100644
--- a/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs
+++ b/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs
@@ -353,17 +353,17 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do
end
end
- # describe "allowed_evm_versions/0" do
+ # describe "allowed_solidity_evm_versions/0" do
# test "returns allowed evm versions defined by ALLOWED_EVM_VERSIONS env var" do
- # Application.put_env(:explorer, :allowed_evm_versions, "CustomEVM1,CustomEVM2,CustomEVM3")
- # response = CodeCompiler.allowed_evm_versions()
+ # Application.put_env(:explorer, :allowed_solidity_evm_versions, "CustomEVM1,CustomEVM2,CustomEVM3")
+ # response = CodeCompiler.evm_versions(:solidity)
# assert ["CustomEVM1", "CustomEVM2", "CustomEVM3"] = response
# end
# test "returns allowed evm versions defined by not trimmed ALLOWED_EVM_VERSIONS env var" do
- # Application.put_env(:explorer, :allowed_evm_versions, "CustomEVM1, CustomEVM2, CustomEVM3")
- # response = CodeCompiler.allowed_evm_versions()
+ # Application.put_env(:explorer, :allowed_solidity_evm_versions, "CustomEVM1, CustomEVM2, CustomEVM3")
+ # response = CodeCompiler.evm_versions(:solidity)
# assert ["CustomEVM1", "CustomEVM2", "CustomEVM3"] = response
# end
@@ -371,11 +371,11 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do
# test "returns default_allowed_evm_versions" do
# Application.put_env(
# :explorer,
- # :allowed_evm_versions,
+ # :allowed_solidity_evm_versions,
# "homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg"
# )
- # response = CodeCompiler.allowed_evm_versions()
+ # response = CodeCompiler.evm_versions(:solidity)
# assert ["homestead", "tangerineWhistle", "spuriousDragon", "byzantium", "constantinople", "petersburg"] = response
# end
diff --git a/apps/explorer/test/explorer/token/instance_metadata_retriever_test.exs b/apps/explorer/test/explorer/token/instance_metadata_retriever_test.exs
index d6a5fb288051..d1d48cc65452 100644
--- a/apps/explorer/test/explorer/token/instance_metadata_retriever_test.exs
+++ b/apps/explorer/test/explorer/token/instance_metadata_retriever_test.exs
@@ -1,8 +1,7 @@
defmodule Explorer.Token.InstanceMetadataRetrieverTest do
use EthereumJSONRPC.Case
- alias EthereumJSONRPC.Encoder
- alias Explorer.Token.InstanceMetadataRetriever
+ alias Indexer.Fetcher.TokenInstance.MetadataRetriever
alias Plug.Conn
import Mox
@@ -90,7 +89,7 @@ defmodule Explorer.Token.InstanceMetadataRetrieverTest do
assert %{
"c87b56dd" => {:ok, ["https://vault.warriders.com/18290729947667102496.json"]}
} ==
- InstanceMetadataRetriever.query_contract(
+ MetadataRetriever.query_contract(
"0x5caebd3b32e210e85ce3e9d51638b9c445481567",
%{
"c87b56dd" => [18_290_729_947_667_102_496]
@@ -133,7 +132,7 @@ defmodule Explorer.Token.InstanceMetadataRetrieverTest do
assert %{
"0e89341c" => {:ok, ["https://vault.warriders.com/18290729947667102496.json"]}
} ==
- InstanceMetadataRetriever.query_contract(
+ MetadataRetriever.query_contract(
"0x5caebd3b32e210e85ce3e9d51638b9c445481567",
%{
"0e89341c" => [18_290_729_947_667_102_496]
@@ -164,28 +163,7 @@ defmodule Explorer.Token.InstanceMetadataRetrieverTest do
end)
assert {:ok, %{metadata: %{"name" => "Sérgio Mendonça"}}} ==
- InstanceMetadataRetriever.fetch_json(%{
- "c87b56dd" => {:ok, ["http://localhost:#{bypass.port}#{path}"]}
- })
- end
-
- test "fetches json metadata for kitties" do
- Application.put_env(:explorer, :http_adapter, Explorer.Mox.HTTPoison)
-
- result =
- "{\"id\":100500,\"name\":\"KittyBlue_2_Lemonade\",\"generation\":20,\"genes\":\"623509754227292470437941473598751240781530569131665917719736997423495595\",\"created_at\":\"2017-12-06T01:56:27.000Z\",\"birthday\":\"2017-12-06T00:00:00.000Z\",\"image_url\":\"https://img.cryptokitties.co/0x06012c8cf97bead5deae237070f9587f8e7a266d/100500.svg\",\"image_url_cdn\":\"https://img.cn.cryptokitties.co/0x06012c8cf97bead5deae237070f9587f8e7a266d/100500.svg\",\"color\":\"strawberry\",\"background_color\":\"#ffe0e5\",\"bio\":\"Shalom! I'm KittyBlue_2_Lemonade. I'm a professional Foreign Film Director and I love cantaloupe. I'm convinced that the world is flat. One day I'll prove it. It's pawesome to meet you!\",\"kitty_type\":null,\"is_fancy\":false,\"is_exclusive\":false,\"is_special_edition\":false,\"fancy_type\":null,\"language\":\"en\",\"is_prestige\":false,\"prestige_type\":null,\"prestige_ranking\":null,\"prestige_time_limit\":null,\"status\":{\"is_ready\":true,\"is_gestating\":false,\"cooldown\":1410310201506,\"dynamic_cooldown\":1475064986478,\"cooldown_index\":10,\"cooldown_end_block\":0,\"pending_tx_type\":null,\"pending_tx_since\":null},\"purrs\":{\"count\":1,\"is_purred\":false},\"watchlist\":{\"count\":0,\"is_watchlisted\":false},\"hatcher\":{\"address\":\"0x7b9ea9ac69b8fde875554321472c732eeff06ca0\",\"image\":\"14\",\"nickname\":\"KittyBlu\",\"hasDapper\":false,\"twitter_id\":null,\"twitter_image_url\":null,\"twitter_handle\":null},\"auction\":{},\"offer\":{},\"owner\":{\"address\":\"0x7b9ea9ac69b8fde875554321472c732eeff06ca0\",\"hasDapper\":false,\"twitter_id\":null,\"twitter_image_url\":null,\"twitter_handle\":null,\"image\":\"14\",\"nickname\":\"KittyBlu\"},\"matron\":{\"id\":46234,\"name\":\"KittyBlue_1_Limegreen\",\"generation\":10,\"enhanced_cattributes\":[{\"type\":\"body\",\"kittyId\":19631,\"position\":105,\"description\":\"cymric\"},{\"type\":\"coloreyes\",\"kittyId\":40356,\"position\":263,\"description\":\"limegreen\"},{\"type\":\"eyes\",\"kittyId\":3185,\"position\":16,\"description\":\"raisedbrow\"},{\"type\":\"pattern\",\"kittyId\":46234,\"position\":-1,\"description\":\"totesbasic\"},{\"type\":\"mouth\",\"kittyId\":46234,\"position\":-1,\"description\":\"happygokitty\"},{\"type\":\"colorprimary\",\"kittyId\":46234,\"position\":-1,\"description\":\"greymatter\"},{\"type\":\"colorsecondary\",\"kittyId\":46234,\"position\":-1,\"description\":\"lemonade\"},{\"type\":\"colortertiary\",\"kittyId\":46234,\"position\":-1,\"description\":\"granitegrey\"}],\"owner_wallet_address\":\"0x7b9ea9ac69b8fde875554321472c732eeff06ca0\",\"owner\":{\"address\":\"0x7b9ea9ac69b8fde875554321472c732eeff06ca0\"},\"created_at\":\"2017-12-03T21:29:17.000Z\",\"image_url\":\"https://img.cryptokitties.co/0x06012c8cf97bead5deae237070f9587f8e7a266d/46234.svg\",\"image_url_cdn\":\"https://img.cn.cryptokitties.co/0x06012c8cf97bead5deae237070f9587f8e7a266d/46234.svg\",\"color\":\"limegreen\",\"is_fancy\":false,\"kitty_type\":null,\"is_exclusive\":false,\"is_special_edition\":false,\"fancy_type\":null,\"status\":{\"is_ready\":true,\"is_gestating\":false,\"cooldown\":1486487069384},\"hatched\":true,\"wrapped\":false,\"image_url_png\":\"https://img.cryptokitties.co/0x06012c8cf97bead5deae237070f9587f8e7a266d/46234.png\"},\"sire\":{\"id\":82090,\"name\":null,\"generation\":19,\"enhanced_cattributes\":[{\"type\":\"body\",\"kittyId\":82090,\"position\":-1,\"description\":\"himalayan\"},{\"type\":\"coloreyes\",\"kittyId\":82090,\"position\":-1,\"description\":\"strawberry\"},{\"type\":\"eyes\",\"kittyId\":82090,\"position\":-1,\"description\":\"thicccbrowz\"},{\"type\":\"pattern\",\"kittyId\":82090,\"position\":-1,\"description\":\"totesbasic\"},{\"type\":\"mouth\",\"kittyId\":82090,\"position\":-1,\"description\":\"pouty\"},{\"type\":\"colorprimary\",\"kittyId\":82090,\"position\":-1,\"description\":\"aquamarine\"},{\"type\":\"colorsecondary\",\"kittyId\":82090,\"position\":-1,\"description\":\"chocolate\"},{\"type\":\"colortertiary\",\"kittyId\":82090,\"position\":-1,\"description\":\"granitegrey\"}],\"owner_wallet_address\":\"0x798fdad0cedc4b298fc7d53a982fa0c5f447eaa5\",\"owner\":{\"address\":\"0x798fdad0cedc4b298fc7d53a982fa0c5f447eaa5\"},\"created_at\":\"2017-12-05T06:30:05.000Z\",\"image_url\":\"https://img.cryptokitties.co/0x06012c8cf97bead5deae237070f9587f8e7a266d/82090.svg\",\"image_url_cdn\":\"https://img.cn.cryptokitties.co/0x06012c8cf97bead5deae237070f9587f8e7a266d/82090.svg\",\"color\":\"strawberry\",\"is_fancy\":false,\"is_exclusive\":false,\"is_special_edition\":false,\"fancy_type\":null,\"status\":{\"is_ready\":true,\"is_gestating\":false,\"cooldown\":1486619010030},\"kitty_type\":null,\"hatched\":true,\"wrapped\":false,\"image_url_png\":\"https://img.cryptokitties.co/0x06012c8cf97bead5deae237070f9587f8e7a266d/82090.png\"},\"children\":[],\"hatched\":true,\"wrapped\":false,\"enhanced_cattributes\":[{\"type\":\"colorprimary\",\"description\":\"greymatter\",\"position\":null,\"kittyId\":100500},{\"type\":\"coloreyes\",\"description\":\"strawberry\",\"position\":null,\"kittyId\":100500},{\"type\":\"body\",\"description\":\"himalayan\",\"position\":null,\"kittyId\":100500},{\"type\":\"colorsecondary\",\"description\":\"lemonade\",\"position\":null,\"kittyId\":100500},{\"type\":\"mouth\",\"description\":\"pouty\",\"position\":null,\"kittyId\":100500},{\"type\":\"pattern\",\"description\":\"totesbasic\",\"position\":null,\"kittyId\":100500},{\"type\":\"eyes\",\"description\":\"thicccbrowz\",\"position\":null,\"kittyId\":100500},{\"type\":\"colortertiary\",\"description\":\"kittencream\",\"position\":null,\"kittyId\":100500},{\"type\":\"secret\",\"description\":\"se5\",\"position\":-1,\"kittyId\":100500},{\"type\":\"purrstige\",\"description\":\"pu20\",\"position\":-1,\"kittyId\":100500}],\"variation\":null,\"variation_ranking\":null,\"image_url_png\":\"https://img.cryptokitties.co/0x06012c8cf97bead5deae237070f9587f8e7a266d/100500.png\",\"items\":[]}"
-
- Explorer.Mox.HTTPoison
- |> expect(:get, fn "https://api.cryptokitties.co/kitties/100500", _headers, _options ->
- {:ok, %HTTPoison.Response{status_code: 200, body: result}}
- end)
-
- {:ok, %{metadata: metadata}} =
- InstanceMetadataRetriever.fetch_metadata("0x06012c8cf97bead5deae237070f9587f8e7a266d", 100_500)
-
- assert Map.get(metadata, "name") == "KittyBlue_2_Lemonade"
-
- Application.put_env(:explorer, :http_adapter, HTTPoison)
+ MetadataRetriever.fetch_json({:ok, ["http://localhost:#{bypass.port}#{path}"]})
end
test "fetches json metadata when HTTP status 301", %{bypass: bypass} do
@@ -212,120 +190,19 @@ defmodule Explorer.Token.InstanceMetadataRetrieverTest do
Conn.resp(conn, 200, json)
end)
- {:ok, %{metadata: metadata}} =
- InstanceMetadataRetriever.fetch_metadata_from_uri("http://localhost:#{bypass.port}#{path}")
+ {:ok, %{metadata: metadata}} = MetadataRetriever.fetch_metadata_from_uri("http://localhost:#{bypass.port}#{path}")
assert Map.get(metadata, "attributes") == Jason.decode!(attributes)
end
- test "replace {id} with actual token_id", %{bypass: bypass} do
- json = """
- {
- "name": "Sérgio Mendonça {id}"
- }
- """
-
- abi =
- [
- %{
- "type" => "function",
- "stateMutability" => "nonpayable",
- "payable" => false,
- "outputs" => [],
- "name" => "tokenURI",
- "inputs" => [
- %{"type" => "string", "name" => "name", "internalType" => "string"}
- ]
- }
- ]
- |> ABI.parse_specification()
- |> Enum.at(0)
-
- encoded_url =
- abi
- |> Encoder.encode_function_call(["http://localhost:#{bypass.port}/api/card/{id}"])
- |> String.replace("4cf12d26", "")
-
- EthereumJSONRPC.Mox
- |> expect(:json_rpc, fn [
- %{
- id: 0,
- jsonrpc: "2.0",
- method: "eth_call",
- params: [
- %{
- data:
- "0xc87b56dd0000000000000000000000000000000000000000000000000000000000000309",
- to: "0x5caebd3b32e210e85ce3e9d51638b9c445481567"
- },
- "latest"
- ]
- }
- ],
- _options ->
- {:ok,
- [
- %{
- id: 0,
- jsonrpc: "2.0",
- error: %{code: -32000, message: "execution reverted"}
- }
- ]}
- end)
- |> expect(:json_rpc, fn [
- %{
- id: 0,
- jsonrpc: "2.0",
- method: "eth_call",
- params: [
- %{
- data:
- "0x0e89341c0000000000000000000000000000000000000000000000000000000000000309",
- to: "0x5caebd3b32e210e85ce3e9d51638b9c445481567"
- },
- "latest"
- ]
- }
- ],
- _options ->
+ test "decodes json file in tokenURI" do
+ data =
{:ok,
[
- %{
- id: 0,
- jsonrpc: "2.0",
- result: encoded_url
- }
+ "data:application/json,{\"name\":\"Home%20Address%20-%200x0000000000C1A6066c6c8B9d63e9B6E8865dC117\",\"description\":\"This%20NFT%20can%20be%20redeemed%20on%20HomeWork%20to%20grant%20a%20controller%20the%20exclusive%20right%20to%20deploy%20contracts%20with%20arbitrary%20bytecode%20to%20the%20designated%20home%20address.\",\"image\":\"data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNDQgNzIiPjxzdHlsZT48IVtDREFUQVsuQntzdHJva2UtbGluZWpvaW46cm91bmR9LkN7c3Ryb2tlLW1pdGVybGltaXQ6MTB9LkR7c3Ryb2tlLXdpZHRoOjJ9LkV7ZmlsbDojOWI5YjlhfS5Ge3N0cm9rZS1saW5lY2FwOnJvdW5kfV1dPjwvc3R5bGU+PGcgdHJhbnNmb3JtPSJtYXRyaXgoMS4wMiAwIDAgMS4wMiA4LjEgMCkiPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xOSAzMmgzNHYyNEgxOXoiLz48ZyBzdHJva2U9IiMwMDAiIGNsYXNzPSJCIEMgRCI+PHBhdGggZmlsbD0iI2E1NzkzOSIgZD0iTTI1IDQwaDl2MTZoLTl6Ii8+PHBhdGggZmlsbD0iIzkyZDNmNSIgZD0iTTQwIDQwaDh2N2gtOHoiLz48cGF0aCBmaWxsPSIjZWE1YTQ3IiBkPSJNNTMgMzJIMTl2LTFsMTYtMTYgMTggMTZ6Ii8+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTE5IDMyaDM0djI0SDE5eiIvPjxwYXRoIGZpbGw9IiNlYTVhNDciIGQ9Ik0yOSAyMWwtNSA1di05aDV6Ii8+PC9nPjwvZz48ZyB0cmFuc2Zvcm09Im1hdHJpeCguODQgMCAwIC44NCA2NSA1KSI+PHBhdGggZD0iTTkuNSAyMi45bDQuOCA2LjRhMy4xMiAzLjEyIDAgMCAxLTMgMi4ybC00LjgtNi40Yy4zLTEuNCAxLjYtMi40IDMtMi4yeiIgZmlsbD0iI2QwY2ZjZSIvPjxwYXRoIGZpbGw9IiMwMTAxMDEiIGQ9Ik00MS43IDM4LjVsNS4xLTYuNSIvPjxwYXRoIGQ9Ik00Mi45IDI3LjhMMTguNCA1OC4xIDI0IDYybDIxLjgtMjcuMyAyLjMtMi44eiIgY2xhc3M9IkUiLz48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDMuNCAyOS4zbC00LjcgNS44Ii8+PHBhdGggZD0iTTQ2LjggMzJjMy4yIDIuNiA4LjcgMS4yIDEyLjEtMy4yczMuNi05LjkuMy0xMi41bC01LjEgNi41LTIuOC0uMS0uNy0yLjcgNS4xLTYuNWMtMy4yLTIuNi04LjctMS4yLTEyLjEgMy4ycy0zLjYgOS45LS4zIDEyLjUiIGNsYXNzPSJFIi8+PHBhdGggZmlsbD0iI2E1NzkzOSIgZD0iTTI3LjMgMjZsMTEuOCAxNS43IDMuNCAyLjQgOS4xIDE0LjQtMy4yIDIuMy0xIC43LTEwLjItMTMuNi0xLjMtMy45LTExLjgtMTUuN3oiLz48cGF0aCBkPSJNMTIgMTkuOWw1LjkgNy45IDEwLjItNy42LTMuNC00LjVzNi44LTUuMSAxMC43LTQuNWMwIDAtNi42LTMtMTMuMyAxLjFTMTIgMTkuOSAxMiAxOS45eiIgY2xhc3M9IkUiLz48ZyBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIGNsYXNzPSJCIEMgRCI+PHBhdGggZD0iTTUyIDU4LjlMNDAuOSA0My4ybC0zLjEtMi4zLTEwLjYtMTQuNy0yLjkgMi4yIDEwLjYgMTQuNyAxLjEgMy42IDExLjUgMTUuNXpNMTIuNSAxOS44bDUuOCA4IDEwLjMtNy40LTMuMy00LjZzNi45LTUgMTAuOC00LjNjMCAwLTYuNi0zLjEtMTMuMy45cy0xMC4zIDcuNC0xMC4zIDcuNHptLTIuNiAyLjlsNC43IDYuNWMtLjUgMS4zLTEuNyAyLjEtMyAyLjJsLTQuNy02LjVjLjMtMS40IDEuNi0yLjQgMy0yLjJ6Ii8+PHBhdGggZD0iTTQxLjMgMzguNWw1LjEtNi41bS0zLjUtMi43bC00LjYgNS44bTguMS0zLjFjMy4yIDIuNiA4LjcgMS4yIDEyLjEtMy4yczMuNi05LjkuMy0xMi41bC01LjEgNi41LTIuOC0uMS0uOC0yLjcgNS4xLTYuNWMtMy4yLTIuNi04LjctMS4yLTEyLjEgMy4yLTMuNCA0LjMtMy42IDkuOS0uMyAxMi41IiBjbGFzcz0iRiIvPjxwYXRoIGQ9Ik0zMC44IDQ0LjRMMTkgNTguOWw0IDMgMTAtMTIuNyIgY2xhc3M9IkYiLz48L2c+PC9nPjwvc3ZnPg==\"}"
]}
- end)
-
- Bypass.expect(
- bypass,
- "GET",
- "/api/card/0000000000000000000000000000000000000000000000000000000000000309",
- fn conn ->
- Conn.resp(conn, 200, json)
- end
- )
-
- assert {:ok,
- %{
- metadata: %{
- "name" => "Sérgio Mendonça 0000000000000000000000000000000000000000000000000000000000000309"
- }
- }} ==
- InstanceMetadataRetriever.fetch_metadata("0x5caebd3b32e210e85ce3e9d51638b9c445481567", 777)
- end
-
- test "decodes json file in tokenURI" do
- data = %{
- "c87b56dd" =>
- {:ok,
- [
- "data:application/json,{\"name\":\"Home%20Address%20-%200x0000000000C1A6066c6c8B9d63e9B6E8865dC117\",\"description\":\"This%20NFT%20can%20be%20redeemed%20on%20HomeWork%20to%20grant%20a%20controller%20the%20exclusive%20right%20to%20deploy%20contracts%20with%20arbitrary%20bytecode%20to%20the%20designated%20home%20address.\",\"image\":\"data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNDQgNzIiPjxzdHlsZT48IVtDREFUQVsuQntzdHJva2UtbGluZWpvaW46cm91bmR9LkN7c3Ryb2tlLW1pdGVybGltaXQ6MTB9LkR7c3Ryb2tlLXdpZHRoOjJ9LkV7ZmlsbDojOWI5YjlhfS5Ge3N0cm9rZS1saW5lY2FwOnJvdW5kfV1dPjwvc3R5bGU+PGcgdHJhbnNmb3JtPSJtYXRyaXgoMS4wMiAwIDAgMS4wMiA4LjEgMCkiPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xOSAzMmgzNHYyNEgxOXoiLz48ZyBzdHJva2U9IiMwMDAiIGNsYXNzPSJCIEMgRCI+PHBhdGggZmlsbD0iI2E1NzkzOSIgZD0iTTI1IDQwaDl2MTZoLTl6Ii8+PHBhdGggZmlsbD0iIzkyZDNmNSIgZD0iTTQwIDQwaDh2N2gtOHoiLz48cGF0aCBmaWxsPSIjZWE1YTQ3IiBkPSJNNTMgMzJIMTl2LTFsMTYtMTYgMTggMTZ6Ii8+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTE5IDMyaDM0djI0SDE5eiIvPjxwYXRoIGZpbGw9IiNlYTVhNDciIGQ9Ik0yOSAyMWwtNSA1di05aDV6Ii8+PC9nPjwvZz48ZyB0cmFuc2Zvcm09Im1hdHJpeCguODQgMCAwIC44NCA2NSA1KSI+PHBhdGggZD0iTTkuNSAyMi45bDQuOCA2LjRhMy4xMiAzLjEyIDAgMCAxLTMgMi4ybC00LjgtNi40Yy4zLTEuNCAxLjYtMi40IDMtMi4yeiIgZmlsbD0iI2QwY2ZjZSIvPjxwYXRoIGZpbGw9IiMwMTAxMDEiIGQ9Ik00MS43IDM4LjVsNS4xLTYuNSIvPjxwYXRoIGQ9Ik00Mi45IDI3LjhMMTguNCA1OC4xIDI0IDYybDIxLjgtMjcuMyAyLjMtMi44eiIgY2xhc3M9IkUiLz48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDMuNCAyOS4zbC00LjcgNS44Ii8+PHBhdGggZD0iTTQ2LjggMzJjMy4yIDIuNiA4LjcgMS4yIDEyLjEtMy4yczMuNi05LjkuMy0xMi41bC01LjEgNi41LTIuOC0uMS0uNy0yLjcgNS4xLTYuNWMtMy4yLTIuNi04LjctMS4yLTEyLjEgMy4ycy0zLjYgOS45LS4zIDEyLjUiIGNsYXNzPSJFIi8+PHBhdGggZmlsbD0iI2E1NzkzOSIgZD0iTTI3LjMgMjZsMTEuOCAxNS43IDMuNCAyLjQgOS4xIDE0LjQtMy4yIDIuMy0xIC43LTEwLjItMTMuNi0xLjMtMy45LTExLjgtMTUuN3oiLz48cGF0aCBkPSJNMTIgMTkuOWw1LjkgNy45IDEwLjItNy42LTMuNC00LjVzNi44LTUuMSAxMC43LTQuNWMwIDAtNi42LTMtMTMuMyAxLjFTMTIgMTkuOSAxMiAxOS45eiIgY2xhc3M9IkUiLz48ZyBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIGNsYXNzPSJCIEMgRCI+PHBhdGggZD0iTTUyIDU4LjlMNDAuOSA0My4ybC0zLjEtMi4zLTEwLjYtMTQuNy0yLjkgMi4yIDEwLjYgMTQuNyAxLjEgMy42IDExLjUgMTUuNXpNMTIuNSAxOS44bDUuOCA4IDEwLjMtNy40LTMuMy00LjZzNi45LTUgMTAuOC00LjNjMCAwLTYuNi0zLjEtMTMuMy45cy0xMC4zIDcuNC0xMC4zIDcuNHptLTIuNiAyLjlsNC43IDYuNWMtLjUgMS4zLTEuNyAyLjEtMyAyLjJsLTQuNy02LjVjLjMtMS40IDEuNi0yLjQgMy0yLjJ6Ii8+PHBhdGggZD0iTTQxLjMgMzguNWw1LjEtNi41bS0zLjUtMi43bC00LjYgNS44bTguMS0zLjFjMy4yIDIuNiA4LjcgMS4yIDEyLjEtMy4yczMuNi05LjkuMy0xMi41bC01LjEgNi41LTIuOC0uMS0uOC0yLjcgNS4xLTYuNWMtMy4yLTIuNi04LjctMS4yLTEyLjEgMy4yLTMuNCA0LjMtMy42IDkuOS0uMyAxMi41IiBjbGFzcz0iRiIvPjxwYXRoIGQ9Ik0zMC44IDQ0LjRMMTkgNTguOWw0IDMgMTAtMTIuNyIgY2xhc3M9IkYiLz48L2c+PC9nPjwvc3ZnPg==\"}"
- ]}
- }
- assert InstanceMetadataRetriever.fetch_json(data) ==
+ assert MetadataRetriever.fetch_json(data) ==
{:ok,
%{
metadata: %{
@@ -339,15 +216,13 @@ defmodule Explorer.Token.InstanceMetadataRetrieverTest do
end
test "decodes base64 encoded json file in tokenURI" do
- data = %{
- "c87b56dd" =>
- {:ok,
- [
- "data:application/json;base64,eyJuYW1lIjogIi54ZGFpIiwgImRlc2NyaXB0aW9uIjogIlB1bmsgRG9tYWlucyBkaWdpdGFsIGlkZW50aXR5LiBWaXNpdCBodHRwczovL3B1bmsuZG9tYWlucy8iLCAiaW1hZ2UiOiAiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhacFpYZENiM2c5SWpBZ01DQTFNREFnTlRBd0lpQjNhV1IwYUQwaU5UQXdJaUJvWldsbmFIUTlJalV3TUNJK1BHUmxabk0rUEd4cGJtVmhja2R5WVdScFpXNTBJR2xrUFNKbmNtRmtJaUI0TVQwaU1DVWlJSGt4UFNJd0pTSWdlREk5SWpFd01DVWlJSGt5UFNJd0pTSStQSE4wYjNBZ2IyWm1jMlYwUFNJd0pTSWdjM1I1YkdVOUluTjBiM0F0WTI5c2IzSTZjbWRpS0RVNExERTNMREV4TmlrN2MzUnZjQzF2Y0dGamFYUjVPakVpSUM4K1BITjBiM0FnYjJabWMyVjBQU0l4TURBbElpQnpkSGxzWlQwaWMzUnZjQzFqYjJ4dmNqcHlaMklvTVRFMkxESTFMREUzS1R0emRHOXdMVzl3WVdOcGRIazZNU0lnTHo0OEwyeHBibVZoY2tkeVlXUnBaVzUwUGp3dlpHVm1jejQ4Y21WamRDQjRQU0l3SWlCNVBTSXdJaUIzYVdSMGFEMGlOVEF3SWlCb1pXbG5hSFE5SWpVd01DSWdabWxzYkQwaWRYSnNLQ05uY21Ga0tTSXZQangwWlhoMElIZzlJalV3SlNJZ2VUMGlOVEFsSWlCa2IyMXBibUZ1ZEMxaVlYTmxiR2x1WlQwaWJXbGtaR3hsSWlCbWFXeHNQU0ozYUdsMFpTSWdkR1Y0ZEMxaGJtTm9iM0k5SW0xcFpHUnNaU0lnWm05dWRDMXphWHBsUFNKNExXeGhjbWRsSWo0dWVHUmhhVHd2ZEdWNGRENDhkR1Y0ZENCNFBTSTFNQ1VpSUhrOUlqY3dKU0lnWkc5dGFXNWhiblF0WW1GelpXeHBibVU5SW0xcFpHUnNaU0lnWm1sc2JEMGlkMmhwZEdVaUlIUmxlSFF0WVc1amFHOXlQU0p0YVdSa2JHVWlQbkIxYm1zdVpHOXRZV2x1Y3p3dmRHVjRkRDQ4TDNOMlp6ND0ifQ=="
- ]}
- }
+ data =
+ {:ok,
+ [
+ "data:application/json;base64,eyJuYW1lIjogIi54ZGFpIiwgImRlc2NyaXB0aW9uIjogIlB1bmsgRG9tYWlucyBkaWdpdGFsIGlkZW50aXR5LiBWaXNpdCBodHRwczovL3B1bmsuZG9tYWlucy8iLCAiaW1hZ2UiOiAiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhacFpYZENiM2c5SWpBZ01DQTFNREFnTlRBd0lpQjNhV1IwYUQwaU5UQXdJaUJvWldsbmFIUTlJalV3TUNJK1BHUmxabk0rUEd4cGJtVmhja2R5WVdScFpXNTBJR2xrUFNKbmNtRmtJaUI0TVQwaU1DVWlJSGt4UFNJd0pTSWdlREk5SWpFd01DVWlJSGt5UFNJd0pTSStQSE4wYjNBZ2IyWm1jMlYwUFNJd0pTSWdjM1I1YkdVOUluTjBiM0F0WTI5c2IzSTZjbWRpS0RVNExERTNMREV4TmlrN2MzUnZjQzF2Y0dGamFYUjVPakVpSUM4K1BITjBiM0FnYjJabWMyVjBQU0l4TURBbElpQnpkSGxzWlQwaWMzUnZjQzFqYjJ4dmNqcHlaMklvTVRFMkxESTFMREUzS1R0emRHOXdMVzl3WVdOcGRIazZNU0lnTHo0OEwyeHBibVZoY2tkeVlXUnBaVzUwUGp3dlpHVm1jejQ4Y21WamRDQjRQU0l3SWlCNVBTSXdJaUIzYVdSMGFEMGlOVEF3SWlCb1pXbG5hSFE5SWpVd01DSWdabWxzYkQwaWRYSnNLQ05uY21Ga0tTSXZQangwWlhoMElIZzlJalV3SlNJZ2VUMGlOVEFsSWlCa2IyMXBibUZ1ZEMxaVlYTmxiR2x1WlQwaWJXbGtaR3hsSWlCbWFXeHNQU0ozYUdsMFpTSWdkR1Y0ZEMxaGJtTm9iM0k5SW0xcFpHUnNaU0lnWm05dWRDMXphWHBsUFNKNExXeGhjbWRsSWo0dWVHUmhhVHd2ZEdWNGRENDhkR1Y0ZENCNFBTSTFNQ1VpSUhrOUlqY3dKU0lnWkc5dGFXNWhiblF0WW1GelpXeHBibVU5SW0xcFpHUnNaU0lnWm1sc2JEMGlkMmhwZEdVaUlIUmxlSFF0WVc1amFHOXlQU0p0YVdSa2JHVWlQbkIxYm1zdVpHOXRZV2x1Y3p3dmRHVjRkRDQ4TDNOMlp6ND0ifQ=="
+ ]}
- assert InstanceMetadataRetriever.fetch_json(data) ==
+ assert MetadataRetriever.fetch_json(data) ==
{:ok,
%{
metadata: %{
@@ -360,15 +235,13 @@ defmodule Explorer.Token.InstanceMetadataRetrieverTest do
end
test "decodes base64 encoded json file (with unicode string) in tokenURI" do
- data = %{
- "c87b56dd" =>
- {:ok,
- [
- "data:application/json;base64,eyJkZXNjcmlwdGlvbiI6ICJQdW5rIERvbWFpbnMgZGlnaXRhbCBpZGVudGl0eSDDry4gVmlzaXQgaHR0cHM6Ly9wdW5rLmRvbWFpbnMvIn0="
- ]}
- }
+ data =
+ {:ok,
+ [
+ "data:application/json;base64,eyJkZXNjcmlwdGlvbiI6ICJQdW5rIERvbWFpbnMgZGlnaXRhbCBpZGVudGl0eSDDry4gVmlzaXQgaHR0cHM6Ly9wdW5rLmRvbWFpbnMvIn0="
+ ]}
- assert InstanceMetadataRetriever.fetch_json(data) ==
+ assert MetadataRetriever.fetch_json(data) ==
{:ok,
%{
metadata: %{
@@ -390,20 +263,18 @@ defmodule Explorer.Token.InstanceMetadataRetrieverTest do
Conn.resp(conn, 200, json)
end)
- data = %{
- "c87b56dd" =>
- {:ok,
- [
- "http://localhost:#{bypass.port}#{path}"
- ]}
- }
+ data =
+ {:ok,
+ [
+ "http://localhost:#{bypass.port}#{path}"
+ ]}
assert {:ok,
%{
metadata: %{
"image" => "https://ipfs.io/ipfs/bafybeig6nlmyzui7llhauc52j2xo5hoy4lzp6442lkve5wysdvjkizxonu"
}
- }} == InstanceMetadataRetriever.fetch_json(data)
+ }} == MetadataRetriever.fetch_json(data)
end
test "Fetches metadata from ipfs", %{bypass: bypass} do
@@ -419,18 +290,16 @@ defmodule Explorer.Token.InstanceMetadataRetrieverTest do
Conn.resp(conn, 200, json)
end)
- data = %{
- "c87b56dd" =>
- {:ok,
- [
- "http://localhost:#{bypass.port}#{path}"
- ]}
- }
+ data =
+ {:ok,
+ [
+ "http://localhost:#{bypass.port}#{path}"
+ ]}
{:ok,
%{
metadata: metadata
- }} = InstanceMetadataRetriever.fetch_json(data)
+ }} = MetadataRetriever.fetch_json(data)
assert "ipfs://bafybeihxuj3gxk7x5p36amzootyukbugmx3pw7dyntsrohg3se64efkuga/51.png" == Map.get(metadata, "image")
end
@@ -438,13 +307,11 @@ defmodule Explorer.Token.InstanceMetadataRetrieverTest do
test "Fetches metadata from '${url}'", %{bypass: bypass} do
path = "/data/8/8578.json"
- data = %{
- "c87b56dd" =>
- {:ok,
- [
- "'http://localhost:#{bypass.port}#{path}'"
- ]}
- }
+ data =
+ {:ok,
+ [
+ "'http://localhost:#{bypass.port}#{path}'"
+ ]}
json = """
{
@@ -467,17 +334,15 @@ defmodule Explorer.Token.InstanceMetadataRetrieverTest do
assert {:ok,
%{
metadata: Jason.decode!(json)
- }} == InstanceMetadataRetriever.fetch_json(data)
+ }} == MetadataRetriever.fetch_json(data)
end
test "Process custom execution reverted" do
- data = %{
- "c87b56dd" =>
- {:error,
- "(3) execution reverted: Nonexistent token (0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000114e6f6e6578697374656e7420746f6b656e000000000000000000000000000000)"}
- }
+ data =
+ {:error,
+ "(3) execution reverted: Nonexistent token (0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000114e6f6e6578697374656e7420746f6b656e000000000000000000000000000000)"}
- assert {:ok, %{error: "VM execution error"}} == InstanceMetadataRetriever.fetch_json(data)
+ assert {:error, "VM execution error"} == MetadataRetriever.fetch_json(data)
end
test "Process CIDv0 IPFS links" do
@@ -507,7 +372,7 @@ defmodule Explorer.Token.InstanceMetadataRetrieverTest do
"name" => "asda",
"salePrice" => 34
}
- }} == InstanceMetadataRetriever.fetch_json(%{"0e89341c" => {:ok, [data]}})
+ }} == MetadataRetriever.fetch_json({:ok, [data]})
Application.put_env(:explorer, :http_adapter, HTTPoison)
end
@@ -553,7 +418,7 @@ defmodule Explorer.Token.InstanceMetadataRetrieverTest do
%{
metadata: Jason.decode!(json)
}} ==
- InstanceMetadataRetriever.fetch_json(%{"0e89341c" => {:ok, ["http://localhost:#{bypass.port}#{path}"]}})
+ MetadataRetriever.fetch_json({:ok, ["http://localhost:#{bypass.port}#{path}"]})
end
end
end
diff --git a/apps/explorer/test/explorer/token_instance_owner_address_migration/helper_test.exs b/apps/explorer/test/explorer/token_instance_owner_address_migration/helper_test.exs
new file mode 100644
index 000000000000..355d161259bb
--- /dev/null
+++ b/apps/explorer/test/explorer/token_instance_owner_address_migration/helper_test.exs
@@ -0,0 +1,121 @@
+defmodule Explorer.TokenInstanceOwnerAddressMigration.HelperTest do
+ use Explorer.DataCase
+
+ alias Explorer.{Chain, Repo}
+ alias Explorer.Chain.Token.Instance
+ alias Explorer.TokenInstanceOwnerAddressMigration.Helper
+
+ {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
+ @burn_address_hash burn_address_hash
+
+ describe "fetch_and_insert/2" do
+ test "successfully update owner of single token instance" do
+ token_address = insert(:contract_address)
+ insert(:token, contract_address: token_address, type: "ERC-721")
+
+ instance = insert(:token_instance, token_contract_address_hash: token_address.hash)
+
+ transaction =
+ :transaction
+ |> insert()
+ |> with_block()
+
+ tt_1 =
+ insert(:token_transfer,
+ token_ids: [instance.token_id],
+ transaction: transaction,
+ token_contract_address: token_address
+ )
+
+ Helper.fetch_and_insert([
+ %{token_id: instance.token_id, token_contract_address_hash: instance.token_contract_address_hash}
+ ])
+
+ owner_address = tt_1.to_address_hash
+ block_number = tt_1.block_number
+ log_index = tt_1.log_index
+
+ assert %Instance{
+ owner_address_hash: ^owner_address,
+ owner_updated_at_block: ^block_number,
+ owner_updated_at_log_index: ^log_index
+ } =
+ Repo.get_by(Instance,
+ token_id: instance.token_id,
+ token_contract_address_hash: instance.token_contract_address_hash
+ )
+ end
+
+ test "put placeholder value if tt absent in db" do
+ instance = insert(:token_instance)
+
+ Helper.fetch_and_insert([
+ %{token_id: instance.token_id, token_contract_address_hash: instance.token_contract_address_hash}
+ ])
+
+ assert %Instance{
+ owner_address_hash: @burn_address_hash,
+ owner_updated_at_block: -1,
+ owner_updated_at_log_index: -1
+ } =
+ Repo.get_by(Instance,
+ token_id: instance.token_id,
+ token_contract_address_hash: instance.token_contract_address_hash
+ )
+ end
+
+ test "update owners of token instances batch" do
+ instances =
+ for _ <- 0..5 do
+ token_address = insert(:contract_address)
+ insert(:token, contract_address: token_address, type: "ERC-721")
+
+ instance = insert(:token_instance, token_contract_address_hash: token_address.hash)
+
+ tt =
+ for _ <- 0..5 do
+ transaction =
+ :transaction
+ |> insert()
+ |> with_block()
+
+ for _ <- 0..5 do
+ insert(:token_transfer,
+ token_ids: [instance.token_id],
+ transaction: transaction,
+ token_contract_address: token_address
+ )
+ end
+ end
+ |> Enum.concat()
+ |> Enum.max_by(fn tt -> {tt.block_number, tt.log_index} end)
+
+ %{
+ token_id: instance.token_id,
+ token_contract_address_hash: instance.token_contract_address_hash,
+ owner_address_hash: tt.to_address_hash,
+ owner_updated_at_block: tt.block_number,
+ owner_updated_at_log_index: tt.log_index
+ }
+ end
+
+ Helper.fetch_and_insert(instances)
+
+ for ti <- instances do
+ owner_address = ti.owner_address_hash
+ block_number = ti.owner_updated_at_block
+ log_index = ti.owner_updated_at_log_index
+
+ assert %Instance{
+ owner_address_hash: ^owner_address,
+ owner_updated_at_block: ^block_number,
+ owner_updated_at_log_index: ^log_index
+ } =
+ Repo.get_by(Instance,
+ token_id: ti.token_id,
+ token_contract_address_hash: ti.token_contract_address_hash
+ )
+ end
+ end
+ end
+end
diff --git a/apps/explorer/test/explorer/token_transfer_token_id_migration/lowest_block_number_updater_test.exs b/apps/explorer/test/explorer/token_transfer_token_id_migration/lowest_block_number_updater_test.exs
deleted file mode 100644
index bdd94920db2c..000000000000
--- a/apps/explorer/test/explorer/token_transfer_token_id_migration/lowest_block_number_updater_test.exs
+++ /dev/null
@@ -1,37 +0,0 @@
-defmodule Explorer.TokenTransferTokenIdMigration.LowestBlockNumberUpdaterTest do
- use Explorer.DataCase, async: false
-
- alias Explorer.Repo
- alias Explorer.TokenTransferTokenIdMigration.LowestBlockNumberUpdater
- alias Explorer.Utility.TokenTransferTokenIdMigratorProgress
-
- describe "Add range and update last processed block number" do
- test "add_range/2" do
- TokenTransferTokenIdMigratorProgress.update_last_processed_block_number(2000, true)
- LowestBlockNumberUpdater.start_link([])
-
- LowestBlockNumberUpdater.add_range(1000, 500)
- LowestBlockNumberUpdater.add_range(1500, 1001)
- Process.sleep(10)
-
- assert %{last_processed_block_number: 2000, processed_ranges: [1500..500//-1]} =
- :sys.get_state(LowestBlockNumberUpdater)
-
- assert %{last_processed_block_number: 2000} = Repo.one(TokenTransferTokenIdMigratorProgress)
-
- LowestBlockNumberUpdater.add_range(499, 300)
- LowestBlockNumberUpdater.add_range(299, 0)
- Process.sleep(10)
-
- assert %{last_processed_block_number: 2000, processed_ranges: [1500..0//-1]} =
- :sys.get_state(LowestBlockNumberUpdater)
-
- assert %{last_processed_block_number: 2000} = Repo.one(TokenTransferTokenIdMigratorProgress)
-
- LowestBlockNumberUpdater.add_range(1999, 1501)
- Process.sleep(10)
- assert %{last_processed_block_number: 0, processed_ranges: []} = :sys.get_state(LowestBlockNumberUpdater)
- assert %{last_processed_block_number: 0} = Repo.one(TokenTransferTokenIdMigratorProgress)
- end
- end
-end
diff --git a/apps/explorer/test/explorer/token_transfer_token_id_migration/worker_test.exs b/apps/explorer/test/explorer/token_transfer_token_id_migration/worker_test.exs
deleted file mode 100644
index 8797e90130e6..000000000000
--- a/apps/explorer/test/explorer/token_transfer_token_id_migration/worker_test.exs
+++ /dev/null
@@ -1,31 +0,0 @@
-defmodule Explorer.TokenTransferTokenIdMigration.WorkerTest do
- use Explorer.DataCase, async: false
-
- alias Explorer.Repo
- alias Explorer.TokenTransferTokenIdMigration.{LowestBlockNumberUpdater, Worker}
- alias Explorer.Utility.TokenTransferTokenIdMigratorProgress
-
- describe "Move TokenTransfer token_id to token_ids" do
- test "Move token_ids and update last processed block number" do
- insert(:token_transfer, block_number: 1, token_id: 1, transaction: insert(:transaction))
- insert(:token_transfer, block_number: 500, token_id: 2, transaction: insert(:transaction))
- insert(:token_transfer, block_number: 1000, token_id: 3, transaction: insert(:transaction))
- insert(:token_transfer, block_number: 1500, token_id: 4, transaction: insert(:transaction))
- insert(:token_transfer, block_number: 2000, token_id: 5, transaction: insert(:transaction))
-
- TokenTransferTokenIdMigratorProgress.update_last_processed_block_number(3000, true)
- LowestBlockNumberUpdater.start_link([])
-
- Worker.start_link(idx: 1, first_block: 0, last_block: 3000, step: 2)
- Worker.start_link(idx: 2, first_block: 0, last_block: 3000, step: 2)
- Worker.start_link(idx: 3, first_block: 0, last_block: 3000, step: 2)
- Process.sleep(200)
-
- token_transfers = Repo.all(Explorer.Chain.TokenTransfer)
- assert Enum.all?(token_transfers, fn tt -> is_nil(tt.token_id) end)
-
- expected_token_ids = [[Decimal.new(1)], [Decimal.new(2)], [Decimal.new(3)], [Decimal.new(4)], [Decimal.new(5)]]
- assert ^expected_token_ids = token_transfers |> Enum.map(& &1.token_ids) |> Enum.sort_by(&List.first/1)
- end
- end
-end
diff --git a/apps/explorer/test/support/data_case.ex b/apps/explorer/test/support/data_case.ex
index da18760983cc..f93e1bcf7aa7 100644
--- a/apps/explorer/test/support/data_case.ex
+++ b/apps/explorer/test/support/data_case.ex
@@ -35,10 +35,18 @@ defmodule Explorer.DataCase do
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo)
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.Account)
+ :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.PolygonEdge)
+ :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.PolygonZkevm)
+ :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.RSK)
+ :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.Suave)
unless tags[:async] do
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()})
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Account, {:shared, self()})
+ Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonEdge, {:shared, self()})
+ Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonZkevm, {:shared, self()})
+ Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.RSK, {:shared, self()})
+ Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Suave, {:shared, self()})
end
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.BlockNumber.child_id())
diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex
index 89799b759e0e..5fdd286ee056 100644
--- a/apps/explorer/test/support/factory.ex
+++ b/apps/explorer/test/support/factory.ex
@@ -9,6 +9,8 @@ defmodule Explorer.Factory do
alias Explorer.Account.{
Identity,
+ TagAddress,
+ TagTransaction,
Watchlist,
WatchlistAddress
}
@@ -44,7 +46,7 @@ defmodule Explorer.Factory do
}
alias Explorer.SmartContract.Helper
-
+ alias Explorer.Tags.{AddressTag, AddressToTag}
alias Explorer.Market.MarketHistory
alias Explorer.Repo
@@ -107,6 +109,26 @@ defmodule Explorer.Factory do
}
end
+ def watchlist_address_db_factory(%{wl_id: id}) do
+ hash = build(:address).hash
+
+ %WatchlistAddress{
+ name: sequence("test"),
+ watchlist_id: id,
+ address_hash: hash,
+ address_hash_hash: hash_to_lower_case_string(hash),
+ watch_coin_input: random_bool(),
+ watch_coin_output: random_bool(),
+ watch_erc_20_input: random_bool(),
+ watch_erc_20_output: random_bool(),
+ watch_erc_721_input: random_bool(),
+ watch_erc_721_output: random_bool(),
+ watch_erc_1155_input: random_bool(),
+ watch_erc_1155_output: random_bool(),
+ notify_email: random_bool()
+ }
+ end
+
def custom_abi_factory do
contract_address_hash = to_string(insert(:contract_address).hash)
@@ -140,6 +162,28 @@ defmodule Explorer.Factory do
%{"name" => sequence("name"), "transaction_hash" => to_string(insert(:transaction).hash)}
end
+ def tag_address_db_factory(%{user: user}) do
+ %TagAddress{name: sequence("name"), address_hash: build(:address).hash, identity_id: user.id}
+ end
+
+ def tag_transaction_db_factory(%{user: user}) do
+ %TagTransaction{name: sequence("name"), tx_hash: insert(:transaction).hash, identity_id: user.id}
+ end
+
+ def address_to_tag_factory do
+ %AddressToTag{
+ tag: build(:address_tag),
+ address: build(:address)
+ }
+ end
+
+ def address_tag_factory do
+ %AddressTag{
+ label: sequence("label"),
+ display_name: sequence("display_name")
+ }
+ end
+
def account_watchlist_address_factory do
hash = build(:address).hash
@@ -148,15 +192,15 @@ defmodule Explorer.Factory do
watchlist: build(:account_watchlist),
address_hash: hash,
address_hash_hash: hash_to_lower_case_string(hash),
- watch_coin_input: true,
- watch_coin_output: true,
- watch_erc_20_input: true,
- watch_erc_20_output: true,
- watch_erc_721_input: true,
- watch_erc_721_output: true,
- watch_erc_1155_input: true,
- watch_erc_1155_output: true,
- notify_email: true
+ watch_coin_input: random_bool(),
+ watch_coin_output: random_bool(),
+ watch_erc_20_input: random_bool(),
+ watch_erc_20_output: random_bool(),
+ watch_erc_721_input: random_bool(),
+ watch_erc_721_output: random_bool(),
+ watch_erc_1155_input: random_bool(),
+ watch_erc_1155_output: random_bool(),
+ notify_email: random_bool()
}
end
@@ -489,7 +533,7 @@ defmodule Explorer.Factory do
%Transaction{index: nil} = transaction,
# The `transaction.block` must be consensus. Non-consensus blocks can only be associated with the
# `transaction_forks`.
- %Block{consensus: true, hash: block_hash, number: block_number},
+ %Block{consensus: true, hash: block_hash, number: block_number, timestamp: timestamp},
collated_params
)
when is_list(collated_params) do
@@ -498,6 +542,8 @@ defmodule Explorer.Factory do
cumulative_gas_used = collated_params[:cumulative_gas_used] || Enum.random(21_000..100_000)
gas_used = collated_params[:gas_used] || Enum.random(21_000..100_000)
status = Keyword.get(collated_params, :status, Enum.random([:ok, :error]))
+ block_timestamp = Keyword.get(collated_params, :block_timestamp, timestamp)
+ block_consensus = Keyword.get(collated_params, :block_consensus, true)
error = (status == :error && collated_params[:error]) || nil
@@ -511,7 +557,9 @@ defmodule Explorer.Factory do
error: error,
gas_used: gas_used,
index: next_transaction_index,
- status: status
+ status: status,
+ block_timestamp: block_timestamp,
+ block_consensus: block_consensus
})
|> Repo.update!()
|> Repo.preload(:block)
@@ -631,8 +679,7 @@ defmodule Explorer.Factory do
index: sequence("log_index", & &1),
second_topic: nil,
third_topic: nil,
- transaction: build(:transaction),
- type: sequence("0x")
+ transaction: build(:transaction)
}
end
@@ -644,7 +691,10 @@ defmodule Explorer.Factory do
decimals: 18,
contract_address: build(:address),
type: "ERC-20",
- cataloged: true
+ cataloged: true,
+ icon_url: sequence("https://example.com/icon"),
+ fiat_value: 10.1,
+ is_verified_via_admin_panel: Enum.random([true, false])
}
end
@@ -760,7 +810,8 @@ defmodule Explorer.Factory do
s: sequence(:transaction_s, & &1),
to_address: build(:address),
v: Enum.random(27..30),
- value: Enum.random(1..100_000)
+ value: Enum.random(1..100_000),
+ block_timestamp: DateTime.utc_now()
}
end
@@ -818,7 +869,8 @@ defmodule Explorer.Factory do
abi: contract_code_info.abi,
contract_code_md5: bytecode_md5,
verified_via_sourcify: Enum.random([true, false]),
- is_vyper_contract: Enum.random([true, false])
+ is_vyper_contract: Enum.random([true, false]),
+ verified_via_eth_bytecode_db: Enum.random([true, false])
}
end
@@ -840,8 +892,20 @@ defmodule Explorer.Factory do
%Instance{
token_contract_address_hash: insert(:token).contract_address_hash,
token_id: sequence("token_id", & &1),
- metadata: %{key: "value"},
- error: nil
+ metadata: %{
+ "key" => sequence("value"),
+ "image_url" => sequence("image_url"),
+ "animation_url" => sequence("image_url"),
+ "external_url" => sequence("external_url")
+ },
+ error: nil,
+ owner_address_hash: insert(:address).hash
+ }
+ end
+
+ def log_index_factory do
+ %{
+ log_index: sequence("token_id", & &1)
}
end
@@ -880,9 +944,60 @@ defmodule Explorer.Factory do
%CurrentTokenBalance{
address: build(:address),
- token_contract_address_hash: insert(:token).contract_address_hash,
+ token_contract_address_hash: insert(:token, type: token_type).contract_address_hash,
block_number: block_number(),
- value: Enum.random(1..100_000),
+ value: Enum.random(1_000_000_000_000_000_000..10_000_000_000_000_000_000),
+ value_fetched_at: DateTime.utc_now(),
+ token_id: token_id,
+ token_type: token_type
+ }
+ end
+
+ def address_current_token_balance_with_token_id_and_fixed_token_type_factory(%{
+ token_type: token_type,
+ address: address,
+ token_id: token_id,
+ token_contract_address_hash: token_contract_address_hash,
+ value: value
+ }) do
+ %CurrentTokenBalance{
+ address: address,
+ token_contract_address_hash: token_contract_address_hash,
+ block_number: block_number(),
+ value: value,
+ value_fetched_at: DateTime.utc_now(),
+ token_id: token_id,
+ token_type: token_type
+ }
+ end
+
+ def address_current_token_balance_with_token_id_and_fixed_token_type_factory(%{
+ token_type: token_type,
+ address: address,
+ token_id: token_id,
+ token_contract_address_hash: token_contract_address_hash
+ }) do
+ %CurrentTokenBalance{
+ address: address,
+ token_contract_address_hash: token_contract_address_hash,
+ block_number: block_number(),
+ value: Enum.random(1_000_000_000_000_000_000..10_000_000_000_000_000_000),
+ value_fetched_at: DateTime.utc_now(),
+ token_id: token_id,
+ token_type: token_type
+ }
+ end
+
+ def address_current_token_balance_with_token_id_and_fixed_token_type_factory(%{
+ token_type: token_type,
+ address: address,
+ token_id: token_id
+ }) do
+ %CurrentTokenBalance{
+ address: address,
+ token_contract_address_hash: insert(:token, type: token_type).contract_address_hash,
+ block_number: block_number(),
+ value: Enum.random(1_000_000_000_000_000_000..10_000_000_000_000_000_000),
value_fetched_at: DateTime.utc_now(),
token_id: token_id,
token_type: token_type
diff --git a/apps/explorer/test/support/fakes/no_op_price_source.ex b/apps/explorer/test/support/fakes/no_op_price_source.ex
new file mode 100644
index 000000000000..b9a460ac8876
--- /dev/null
+++ b/apps/explorer/test/support/fakes/no_op_price_source.ex
@@ -0,0 +1,12 @@
+defmodule Explorer.ExchangeRates.Source.NoOpPriceSource do
+ @moduledoc false
+
+ alias Explorer.Market.History.Source.Price, as: SourcePrice
+
+ @behaviour SourcePrice
+
+ @impl SourcePrice
+ def fetch_price_history(_previous_days) do
+ {:ok, []}
+ end
+end
diff --git a/apps/explorer/test/support/fakes/one_coin_source.ex b/apps/explorer/test/support/fakes/one_coin_source.ex
index 4efe5d486e6c..4301f24635f1 100644
--- a/apps/explorer/test/support/fakes/one_coin_source.ex
+++ b/apps/explorer/test/support/fakes/one_coin_source.ex
@@ -16,6 +16,7 @@ defmodule Explorer.ExchangeRates.Source.OneCoinSource do
last_updated: Timex.now(),
name: "",
market_cap_usd: Decimal.new(10_000_000),
+ tvl_usd: Decimal.new(100_500_000),
symbol: Explorer.coin(),
usd_value: Decimal.new(1),
volume_24h_usd: Decimal.new(1)
diff --git a/apps/explorer/test/support/fixture/chain_spec/optimism_genesis.json b/apps/explorer/test/support/fixture/chain_spec/optimism_genesis.json
new file mode 100644
index 000000000000..43b203dda259
--- /dev/null
+++ b/apps/explorer/test/support/fixture/chain_spec/optimism_genesis.json
@@ -0,0 +1,39 @@
+{
+ "commit": "373e1edbbaa2e2b5e7575eba1d3ac3c3431f5d15",
+ "config": {
+ "chainId": 10,
+ "homesteadBlock": 0,
+ "eip150Block": 0,
+ "eip155Block": 0,
+ "eip158Block": 0,
+ "byzantiumBlock": 0,
+ "constantinopleBlock": 0,
+ "petersburgBlock": 0,
+ "istanbulBlock": 0,
+ "muirGlacierBlock": 0,
+ "berlinBlock": 3950000,
+ "clique": {
+ "period": 0,
+ "epoch": 30000
+ }
+ },
+ "difficulty": "1",
+ "gasLimit": "15000000",
+ "extradata": "0x000000000000000000000000000000000000000000000000000000000000000000000398232E2064F896018496b4b44b3D62751F0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "alloc": {
+ "74d6b50283ac1d651f9afdc33521e4c1e3332b78": {
+ "balance": "",
+ "nonce": "1",
+ "code": "0x608060405234801561001057600080fd5b506004361061011d5760003560e01c8063245a7bfc14610122578063313ce5671461014657806350d25bcd1461016457806354fd4d501461017e57806358303b10146101865780636001ac53146101a5578063668a0f021461020f5780637284e4161461021757806379ba5097146102945780638205bf6a1461029e5780638da5cb5b146102a65780638f6b4d91146102ae57806392eefe9b146102b65780639a6fc8f5146102dc578063a928c09614610302578063b5ab58dc14610328578063b633620c14610345578063bc43cbaf14610362578063c15973041461036a578063e8c4be301461038b578063f2fde38b14610393578063f8a2abd3146103b9578063feaf968c146103df575b600080fd5b61012a6103e7565b604080516001600160a01b039092168252519081900360200190f35b61014e6103fc565b6040805160ff9092168252519081900360200190f35b61016c610480565b60408051918252519081900360200190f35b61016c610588565b61018e6105db565b6040805161ffff9092168252519081900360200190f35b6101cb600480360360208110156101bb57600080fd5b50356001600160501b03166105e5565b60405180866001600160501b03168152602001858152602001848152602001838152602001826001600160501b031681526020019550505050505060405180910390f35b61016c61074e565b61021f610850565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610259578181015183820152602001610241565b50505050905090810190601f1680156102865780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61029c610993565b005b61016c610a51565b61012a610b53565b6101cb610b62565b61029c600480360360208110156102cc57600080fd5b50356001600160a01b0316610cc9565b6101cb600480360360208110156102f257600080fd5b50356001600160501b0316610cf4565b61029c6004803603602081101561031857600080fd5b50356001600160a01b0316610dff565b61016c6004803603602081101561033e57600080fd5b5035610ed7565b61016c6004803603602081101561035b57600080fd5b5035610fe1565b61012a6110e4565b61012a6004803603602081101561038057600080fd5b503561ffff166110f3565b61012a611116565b61029c600480360360208110156103a957600080fd5b50356001600160a01b0316611125565b61029c600480360360208110156103cf57600080fd5b50356001600160a01b0316611181565b6101cb6111e3565b6004546201000090046001600160a01b031690565b6000600460000160029054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b15801561044f57600080fd5b505afa158015610463573d6000803e3d6000fd5b505050506040513d602081101561047957600080fd5b5051905090565b6005546000906001600160a01b031680158061053d575060408051630d629b5f60e31b815233600482018181526024830193845236604484018190526001600160a01b03861694636b14daf8946000939190606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b15801561051057600080fd5b505afa158015610524573d6000803e3d6000fd5b505050506040513d602081101561053a57600080fd5b50515b61057a576040805162461bcd60e51b81526020600482015260096024820152684e6f2061636365737360b81b604482015290519081900360640190fd5b6105826112ed565b91505090565b6000600460000160029054906101000a90046001600160a01b03166001600160a01b03166354fd4d506040518163ffffffff1660e01b815260040160206040518083038186803b15801561044f57600080fd5b60045461ffff1690565b60055460009081908190819081906001600160a01b03168015806106aa575060408051630d629b5f60e31b815233600482018181526024830193845236604484018190526001600160a01b03861694636b14daf8946000939190606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b15801561067d57600080fd5b505afa158015610691573d6000803e3d6000fd5b505050506040513d60208110156106a757600080fd5b50515b6106e7576040805162461bcd60e51b81526020600482015260096024820152684e6f2061636365737360b81b604482015290519081900360640190fd5b6002546001600160a01b0316610732576040805162461bcd60e51b815260206004820152601e6024820152600080516020611b2c833981519152604482015290519081900360640190fd5b61073b87611340565b939b929a50909850965090945092505050565b6005546000906001600160a01b031680158061080b575060408051630d629b5f60e31b815233600482018181526024830193845236604484018190526001600160a01b03861694636b14daf8946000939190606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b1580156107de57600080fd5b505afa1580156107f2573d6000803e3d6000fd5b505050506040513d602081101561080857600080fd5b50515b610848576040805162461bcd60e51b81526020600482015260096024820152684e6f2061636365737360b81b604482015290519081900360640190fd5b61058261143d565b6060600460000160029054906101000a90046001600160a01b03166001600160a01b0316637284e4166040518163ffffffff1660e01b815260040160006040518083038186803b1580156108a357600080fd5b505afa1580156108b7573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405260208110156108e057600080fd5b8101908080516040519392919084600160201b8211156108ff57600080fd5b90830190602082018581111561091457600080fd5b8251600160201b81118282018810171561092d57600080fd5b82525081516020918201929091019080838360005b8381101561095a578181015183820152602001610942565b50505050905090810190601f1680156109875780820380516001836020036101000a031916815260200191505b50604052505050905090565b61099b6114ec565b6001600160a01b0316336001600160a01b0316146109f9576040805162461bcd60e51b815260206004820152601660248201527526bab9ba10313290383937b837b9b2b21037bbb732b960511b604482015290519081900360640190fd5b6000610a036114fb565b9050610a0e3361150a565b610a18600061152c565b60405133906001600160a01b038316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a350565b6005546000906001600160a01b0316801580610b0e575060408051630d629b5f60e31b815233600482018181526024830193845236604484018190526001600160a01b03861694636b14daf8946000939190606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b158015610ae157600080fd5b505afa158015610af5573d6000803e3d6000fd5b505050506040513d6020811015610b0b57600080fd5b50515b610b4b576040805162461bcd60e51b81526020600482015260096024820152684e6f2061636365737360b81b604482015290519081900360640190fd5b61058261154e565b6000610b5d6114fb565b905090565b60055460009081908190819081906001600160a01b0316801580610c27575060408051630d629b5f60e31b815233600482018181526024830193845236604484018190526001600160a01b03861694636b14daf8946000939190606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b158015610bfa57600080fd5b505afa158015610c0e573d6000803e3d6000fd5b505050506040513d6020811015610c2457600080fd5b50515b610c64576040805162461bcd60e51b81526020600482015260096024820152684e6f2061636365737360b81b604482015290519081900360640190fd5b6002546001600160a01b0316610caf576040805162461bcd60e51b815260206004820152601e6024820152600080516020611b2c833981519152604482015290519081900360640190fd5b610cb76115a1565b95509550955095509550509091929394565b610cd233611697565b600580546001600160a01b0319166001600160a01b0392909216919091179055565b60055460009081908190819081906001600160a01b0316801580610db9575060408051630d629b5f60e31b815233600482018181526024830193845236604484018190526001600160a01b03861694636b14daf8946000939190606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b158015610d8c57600080fd5b505afa158015610da0573d6000803e3d6000fd5b505050506040513d6020811015610db657600080fd5b50515b610df6576040805162461bcd60e51b81526020600482015260096024820152684e6f2061636365737360b81b604482015290519081900360640190fd5b61073b87611700565b610e0833611697565b6002546001600160a01b03828116911614610e68576040805162461bcd60e51b815260206004820152601b60248201527a24b73b30b634b210383937b837b9b2b21030b3b3b932b3b0ba37b960291b604482015290519081900360640190fd5b600454600280546001600160a01b03191690556201000090046001600160a01b0316610e93826117f9565b816001600160a01b0316816001600160a01b03167f33745f67a407dcb785417f9c123dd3641479a102674b6e35c1f10975625b90e960405160405180910390a35050565b6005546000906001600160a01b0316801580610f94575060408051630d629b5f60e31b815233600482018181526024830193845236604484018190526001600160a01b03861694636b14daf8946000939190606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b158015610f6757600080fd5b505afa158015610f7b573d6000803e3d6000fd5b505050506040513d6020811015610f9157600080fd5b50515b610fd1576040805162461bcd60e51b81526020600482015260096024820152684e6f2061636365737360b81b604482015290519081900360640190fd5b610fda83611868565b9392505050565b6005546000906001600160a01b031680158061109e575060408051630d629b5f60e31b815233600482018181526024830193845236604484018190526001600160a01b03861694636b14daf8946000939190606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b15801561107157600080fd5b505afa158015611085573d6000803e3d6000fd5b505050506040513d602081101561109b57600080fd5b50515b6110db576040805162461bcd60e51b81526020600482015260096024820152684e6f2061636365737360b81b604482015290519081900360640190fd5b610fda83611942565b6005546001600160a01b031681565b61ffff81166000908152600360205260409020546001600160a01b03165b919050565b6002546001600160a01b031690565b61112e33611697565b6111378161152c565b806001600160a01b03166111496114fb565b6001600160a01b03167fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae127860405160405180910390a350565b61118a33611697565b600280546001600160a01b0319166001600160a01b0383811691821790925560045460405191926201000090910416907fc0f151710f03d713b71d9970cee0d5b11ddc9a7552abaa3f6ee818010f21600d90600090a350565b60055460009081908190819081906001600160a01b03168015806112a8575060408051630d629b5f60e31b815233600482018181526024830193845236604484018190526001600160a01b03861694636b14daf8946000939190606401848480828437600083820152604051601f909101601f1916909201965060209550909350505081840390508186803b15801561127b57600080fd5b505afa15801561128f573d6000803e3d6000fd5b505050506040513d60208110156112a557600080fd5b50515b6112e5576040805162461bcd60e51b81526020600482015260096024820152684e6f2061636365737360b81b604482015290519081900360640190fd5b610cb76119e7565b6000600460000160029054906101000a90046001600160a01b03166001600160a01b03166350d25bcd6040518163ffffffff1660e01b815260040160206040518083038186803b15801561044f57600080fd5b60025460009081908190819081906001600160a01b0316611396576040805162461bcd60e51b815260206004820152601e6024820152600080516020611b2c833981519152604482015290519081900360640190fd5b60025460408051639a6fc8f560e01b81526001600160501b038916600482015290516001600160a01b0390921691639a6fc8f59160248082019260a092909190829003018186803b1580156113ea57600080fd5b505afa1580156113fe573d6000803e3d6000fd5b505050506040513d60a081101561141457600080fd5b508051602082015160408301516060840151608090940151929a91995097509195509350915050565b6000611447611b14565b506040805180820182526004805461ffff8116808452620100009091046001600160a01b031660208085018290528551633345078160e11b8152955194956114dd959394929363668a0f02938281019392829003018186803b1580156114ac57600080fd5b505afa1580156114c0573d6000803e3d6000fd5b505050506040513d60208110156114d657600080fd5b5051611abc565b6001600160501b031691505090565b6001546001600160a01b031690565b6000546001600160a01b031690565b600080546001600160a01b0319166001600160a01b0392909216919091179055565b600180546001600160a01b0319166001600160a01b0392909216919091179055565b6000600460000160029054906101000a90046001600160a01b03166001600160a01b0316638205bf6a6040518163ffffffff1660e01b815260040160206040518083038186803b15801561044f57600080fd5b60025460009081908190819081906001600160a01b03166115f7576040805162461bcd60e51b815260206004820152601e6024820152600080516020611b2c833981519152604482015290519081900360640190fd5b600260009054906101000a90046001600160a01b03166001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a06040518083038186803b15801561164557600080fd5b505afa158015611659573d6000803e3d6000fd5b505050506040513d60a081101561166f57600080fd5b5080516020820151604083015160608401516080909401519299919850965091945092509050565b61169f6114fb565b6001600160a01b0316816001600160a01b0316146116fd576040805162461bcd60e51b815260206004820152601660248201527527b7363c9031b0b63630b1363290313c9037bbb732b960511b604482015290519081900360640190fd5b50565b600080600080600080600061171d886001600160501b0316611ad6565b61ffff821660009081526003602052604090819020548151639a6fc8f560e01b81526001600160401b038416600482015291519395509193506001600160a01b0390911691639a6fc8f59160248082019260a092909190829003018186803b15801561178857600080fd5b505afa15801561179c573d6000803e3d6000fd5b505050506040513d60a08110156117b257600080fd5b508051602082015160408301516060840151608090940151929a50909850965090945092506117e5878787878787611ade565b939c929b5090995097509095509350505050565b60048054604080518082018252600161ffff80851691909101168082526001600160a01b0395909516602091820181905261ffff19909316851762010000600160b01b0319166201000084021790935560009384526003909252912080546001600160a01b0319169091179055565b60006001600160501b0382111561188157506000611111565b60008061188d84611ad6565b61ffff821660009081526003602052604090205491935091506001600160a01b0316806118c05760009350505050611111565b806001600160a01b031663b5ab58dc836040518263ffffffff1660e01b815260040180826001600160401b0316815260200191505060206040518083038186803b15801561190d57600080fd5b505afa158015611921573d6000803e3d6000fd5b505050506040513d602081101561193757600080fd5b505195945050505050565b60006001600160501b0382111561195b57506000611111565b60008061196784611ad6565b61ffff821660009081526003602052604090205491935091506001600160a01b03168061199a5760009350505050611111565b806001600160a01b031663b633620c836040518263ffffffff1660e01b815260040180826001600160401b0316815260200191505060206040518083038186803b15801561190d57600080fd5b60008060008060006119f7611b14565b506040805180820182526004805461ffff811683526201000090046001600160a01b0316602083018190528351633fabe5a360e21b815293519293909263feaf968c928281019260a0929190829003018186803b158015611a5757600080fd5b505afa158015611a6b573d6000803e3d6000fd5b505050506040513d60a0811015611a8157600080fd5b5080516020820151604083015160608401516080909401518551939a509198509650919450909250610cb79087908790879087908790611ade565b6001600160401b031660409190911b61ffff60401b161790565b604081901c91565b6000806000806000611af0868c611abc565b8a8a8a611afd8a8c611abc565b939f929e50909c509a509098509650505050505050565b60408051808201909152600080825260208201529056fe4e6f2070726f706f7365642061676772656761746f722070726573656e740000a2646970667358221220e01c79ee01ff8ee5600a342cb2ed15231329d7f3f090fbe7605e1dc18906a86964736f6c634300060c0033",
+ "storage": {
+ "0000000000000000000000000000000000000000000000000000000000000000": "3e074f864b15132401dc85371a683a7a02e61059",
+ "0000000000000000000000000000000000000000000000000000000000000004": "8ce8c13d816fe6daf12d6fd9e4952e1fc88850af0001",
+ "a15bc60c955c405d20d9149c709e2460f1c2d9a497496a7f46004d1772c3054c": "8ce8c13d816fe6daf12d6fd9e4952e1fc88850af"
+ }
+ },
+ "fefd3451e86c275a499e4d4b9efe5424ef2ab86e": {
+ "nonce": "1",
+ "balance": "1"
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/explorer/test/support/fixture/chain_spec/polygon_genesis.json b/apps/explorer/test/support/fixture/chain_spec/polygon_genesis.json
new file mode 100644
index 000000000000..7a338caa78bc
--- /dev/null
+++ b/apps/explorer/test/support/fixture/chain_spec/polygon_genesis.json
@@ -0,0 +1,108 @@
+{
+ "name": "polygon-edge",
+ "genesis": {
+ "nonce": "0x0000000000000000",
+ "timestamp": "0x0",
+ "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000f90305f90299f90294f8a39461324166b0202db1e7502924326262274fa4358fb88006d8d9e6af67c28e85ac400b72c2e635e83234f8a380865e050a206554049a222c4792120d84977a6ca669df56ff3a1cf1cfeccddb650e7aacff4ed6c1d4e37b055858209f80117b3c0a6e7a28e456d4caf2270f430f9df2ba37221f23e9bbd313c9ef488e1849cc5c40d18284d019dde5ed86770309b9c24b70ceff6167a6ca8ad3c21bcecceda100000001f8a394fe5e166ba5ea50c04fca00b07b59966e6c2e9570b8800601da8856a6d3d3bb0f3bcbb90ea7b8c0db8271b9203e6123c6804aa3fc5f810be33287968ca1af2be11839516850a6ffef2337d99e679b7531efbbea2e3bf727a053c0cbede71da3d5f489b6ad862ccd8bb0bfb7fa379e3395d3b1142594a73020e87d63c298a3a4eba0ace65727f8659bab6389b9448b72512db72bbe937f8ad3c21bcecceda100000001f8a3949abb8441a12d4fd8d505c3fc50cddc45e0df2b1eb88017c26d9d91dddc3c1318b20a1ddb3322ea1f4e4415c27e9011d706e7407eed672837173d1909cbff6ccdfd110af3b18bdfea878e8120fdb5bae70dc7a044a2f40aa8f118b41704896f474f80fff52d9047fa8e4a464ac86f9d05a0220975d8440e20c6307d866137053cabd4baf6ba84bfa4a22f5f9297c1bfc2380c235352108ad3c21bcecceda100000001f8a394cab5aac79bebe326e0c80d72b5662e73f5d8ea56b8801d7bb7d44a2f0ebeae2f4380f88188080de34635d78a36647f0704c7b70de7291e2e3b9a1ef699a078c6cd9bb816ea2917c2c2fc699c6248f1f7812a167caf7e15361ec16df56d194768d57c79897c681c96f4321651464f7b577d08083d8b67213a1e29dc8495d8389e6cbd85fdd738c402a1801198b57b302e0e00dfaf12478ad3c21bcecceda100000001c080c0c0f8658080a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000",
+ "gasLimit": "0x989680",
+ "difficulty": "0x0",
+ "mixHash": "0xadce6e5230abe012342a44e4e9b6d05997d6f015387ae0e59be924afc7ec70c1",
+ "coinbase": "0x0000000000000000000000000000000000000000",
+ "alloc": {
+ "0x0000000000000000000000000000000000000101": {
+ "code": "0x608060405234801561001057600080fd5b50600436106101b05760003560e01c806370a08231116100ef578063ce513b6f11610092578063ce513b6f14610398578063dd62ed3e146103ab578063e0563ab1146103be578063ea0fee4f146103c7578063eacdc5ff146103cf578063eeb49945146103d8578063f3f43703146103eb578063fd242c14146103fe57600080fd5b806370a08231146102e7578063947287cf146102fa57806395d89b411461030357806397e5230d1461030b578063981b24d014610315578063a457c2d714610328578063a9059cbb1461033b578063c6b61e4c1461034e57600080fd5b8063395093511161015757806339509351146102735780633b878c22146102865780633ccfd60b1461028f5780633fd50001146102975780634ee2cd7e146102aa57806351351d53146102bd57806361cc2763146102cb57806362656003146102de57600080fd5b806306fdde03146101b5578063095ea7b3146101d35780630f50287c146101f657806318160ddd1461020b57806323b872dd1461021d578063284017f5146102305780632e17de7814610251578063313ce56714610264575b600080fd5b6101bd610411565b6040516101ca91906119ba565b60405180910390f35b6101e66101e13660046119e2565b6104a3565b60405190151581526020016101ca565b610209610204366004611a0e565b6104bd565b005b6035545b6040519081526020016101ca565b6101e661022b366004611a46565b61074f565b61023961202081565b6040516001600160a01b0390911681526020016101ca565b61020961025f366004611a87565b610773565b604051601281526020016101ca565b6101e66102813660046119e2565b61078a565b61023961101081565b6102096107ac565b61020f6102a5366004611a87565b6108bd565b61020f6102b83660046119e2565b6108de565b6102396002600160a01b0381565b6102096102d9366004611b10565b6108f1565b61020f60cc5481565b61020f6102f5366004611c29565b610b1f565b61020f61520881565b6101bd610b3a565b61020f620249f081565b61020f610323366004611a87565b610b49565b6101e66103363660046119e2565b610b54565b6101e66103493660046119e2565b610bcf565b61037d61035c366004611a87565b60ce6020526000908152604090208054600182015460029092015490919083565b604080519384526020840192909252908201526060016101ca565b61020f6103a6366004611c29565b610bdd565b61020f6103b9366004611c46565b610c0b565b61023961203081565b61020f600181565b61020f60cd5481565b6102096103e6366004611c7f565b610c36565b61020f6103f9366004611c29565b610d08565b61020f61040c366004611a87565b610d2f565b60606036805461042090611d08565b80601f016020809104026020016040519081016040528092919081815260200182805461044c90611d08565b80156104995780601f1061046e57610100808354040283529160200191610499565b820191906000526020600020905b81548152906001019060200180831161047c57829003601f168201915b5050505050905090565b6000336104b1818585610d79565b60019150505b92915050565b336002600160a01b03146105065760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b60448201526064015b60405180910390fd5b60cd80546000918261051783611d58565b9190505590508083146105625760405162461bcd60e51b815260206004820152601360248201527215539156141150d5115117d15413d0d217d251606a1b60448201526064016104fd565b81356020830135116105ac5760405162461bcd60e51b81526020600482015260136024820152721393d7d09313d0d2d4d7d0d3d3535255151151606a1b60448201526064016104fd565b60cc546105be83356020850135611d71565b6105c9906001611d84565b6105d39190611dad565b1561062e5760405162461bcd60e51b815260206004820152602560248201527f45504f43485f4d5553545f42455f444956495349424c455f42595f45504f43486044820152645f53495a4560d81b60648201526084016104fd565b813560ce600061063f600185611d71565b815260200190815260200160002060010154600161065d9190611d84565b146106a05760405162461bcd60e51b8152602060048201526013602482015272494e56414c49445f53544152545f424c4f434b60681b60448201526064016104fd565b600081815260ce6020526040902082906106d182828135815560208201356001820155604082013560028201555050565b505060cf80546001810182556000919091526020838101357facb8d954e2cfef495862221e91bd7523613cf8808827cb33edfe4904cc51bf299092018290556040805190850135815284359186917f0ce8712c4dee4bd5a691f0bc1c39594671591e77395f8ebf6a3fb5f63fbea66a910160405180910390a4505050565b60003361075d858285610e9e565b610768858585610f12565b506001949350505050565b61077d33826110b6565b61078733826111e1565b50565b6000336104b181858561079d8383610c0b565b6107a79190611d84565b610d79565b33600090815260d06020526040812060cd5490919081906107ce90849061125a565b808555604051828152919350915033907f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b659060200160405180910390a260c95460cb54604080517f8ca9a95e41b5eece253c93f5b31eed1253aed6b145d8a6e14d913fdf8e7322936020820152338183015260608082018790528251808303909101815260808201928390526316f1983160e01b9092526001600160a01b03938416936316f198319361088693911691608401611dc1565b600060405180830381600087803b1580156108a057600080fd5b505af11580156108b4573d6000803e3d6000fd5b50505050505050565b60cf81815481106108cd57600080fd5b600091825260209091200154905081565b60006108ea83836112cc565b9392505050565b600054610100900460ff16158080156109115750600054600160ff909116105b8061092b5750303b15801561092b575060005460ff166001145b61098e5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016104fd565b6000805460ff1916600117905580156109b1576000805461ff0019166101001790555b6109fb6040518060400160405280600c81526020016b15985b1a59185d1bdc94d95d60a21b815250604051806040016040528060048152602001631594d15560e21b815250611315565b60c980546001600160a01b038089166001600160a01b03199283161790925560ca805488841690831617905560cb80549287169290911691909117905560cc83905560005b8251811015610a9557610a8d838281518110610a5e57610a5e611de5565b602002602001015160000151848381518110610a7c57610a7c611de5565b60200260200101516020015161134a565b600101610a40565b5060cf80546001818101835560009283527facb8d954e2cfef495862221e91bd7523613cf8808827cb33edfe4904cc51bf299091019190915560cd558015610b17576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050565b6001600160a01b031660009081526033602052604090205490565b60606037805461042090611d08565b60006104b782611354565b60003381610b628286610c0b565b905083811015610bc25760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084016104fd565b6107688286868403610d79565b6000336104b1818585610f12565b60cd546001600160a01b038216600090815260d0602052604081209091610c04919061125a565b5092915050565b6001600160a01b03918216600090815260346020908152604080832093909416825291909152205490565b60ca546001600160a01b031633148015610c5d575060cb546001600160a01b038481169116145b610c9a5760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a2a72222a960911b60448201526064016104fd565b7f1bcc0f4c3fad314e585165815f94ecca9b96690a26d6417d7876448a9a867a69610cc9602060008486611dfb565b610cd291611e25565b03610d0257600080610ce78360208187611dfb565b810190610cf491906119e2565b91509150610b17828261134a565b50505050565b60cd546001600160a01b038216600090815260d06020526040812090916104b7919061137f565b600081815260ce60205260408120600101548015610d7057600083815260ce6020526040902054610d609082611d71565b610d6b906001611d84565b6108ea565b60009392505050565b6001600160a01b038316610ddb5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016104fd565b6001600160a01b038216610e3c5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016104fd565b6001600160a01b0383811660008181526034602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b6000610eaa8484610c0b565b90506000198114610d025781811015610f055760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016104fd565b610d028484848403610d79565b6001600160a01b038316610f765760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016104fd565b6001600160a01b038216610fd85760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016104fd565b610fe383838361141d565b6001600160a01b0383166000908152603360205260409020548181101561105b5760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b60648201526084016104fd565b6001600160a01b038085166000818152603360205260408082208686039055928616808252908390208054860190559151600080516020611fd6833981519152906110a99086815260200190565b60405180910390a3610d02565b6001600160a01b0382166111165760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b60648201526084016104fd565b6111228260008361141d565b6001600160a01b038216600090815260336020526040902054818110156111965760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b60648201526084016104fd565b6001600160a01b0383166000818152603360209081526040808320868603905560358054879003905551858152919291600080516020611fd68339815191529101610e91565b505050565b61121381600160cd546111f49190611d84565b6001600160a01b038516600090815260d0602052604090209190611486565b816001600160a01b03167f655c1cd0236fb6dc4916f34c8ff10e3b18fcaea5b344dfc16c36fbb1bdfc5df28260405161124e91815260200190565b60405180910390a25050565b81546000905b83600101548110156112c5576000818152600285016020908152604091829020825180840190935280548352600101549082018190528410156112a357506112c5565b80516112af9084611d84565b92505080806112bd90611d58565b915050611260565b9250929050565b6001600160a01b0382166000908152606560205260408120819081906112f39085906115b1565b915091508161130a5761130585610b1f565b61130c565b805b95945050505050565b600054610100900460ff1661133c5760405162461bcd60e51b81526004016104fd90611e43565b611346828261169f565b5050565b61134682826116df565b60008060006113648460666115b1565b915091508161137557603554611377565b805b949350505050565b60018201546000908082036113985760009150506104b7565b60006113a5600183611d71565b90505b845481106114155760008181526002860160209081526040918290208251808401909352805483526001015490820181905285106113e65750611415565b80516113f29085611d84565b9350816000036114025750611415565b508061140d81611e8e565b9150506113a8565b505092915050565b6001600160a01b038316158061143a57506001600160a01b038216155b61147b5760405162461bcd60e51b81526020600482015260126024820152712a2920a729a322a92fa327a92124a22222a760711b60448201526064016104fd565b6111dc83838361179a565b8160000361149657611496611ea5565b825460018401548181036114ed576040805180820182528581526020808201868152600085815260028a0190925292812091518255915160019182015586018054916114e183611d58565b91905055505050505050565b600060028601816114ff600185611d71565b81526020019081526020016000206001015490508084101561152357611523611ea5565b83811015611572576040805180820182528681526020808201878152600086815260028b01909252928120915182559151600191820155870180549161156883611d58565b9190505550610b17565b84600287016000611584600186611d71565b815260200190815260200160002060000160008282546115a49190611d84565b9091555050505050505050565b600080600084116115fd5760405162461bcd60e51b815260206004820152601660248201527504552433230536e617073686f743a20696420697320360541b60448201526064016104fd565b60cd5484111561164f5760405162461bcd60e51b815260206004820152601d60248201527f4552433230536e617073686f743a206e6f6e6578697374656e7420696400000060448201526064016104fd565b600061165b84866117e2565b845490915081036116735760008092509250506112c5565b600184600101828154811061168a5761168a611de5565b906000526020600020015492509250506112c5565b600054610100900460ff166116c65760405162461bcd60e51b81526004016104fd90611e43565b60366116d28382611f01565b5060376111dc8282611f01565b6001600160a01b0382166117355760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016104fd565b6117416000838361141d565b80603560008282546117539190611d84565b90915550506001600160a01b038216600081815260336020908152604080832080548601905551848152600080516020611fd6833981519152910160405180910390a35050565b6001600160a01b0383166117b9576117b18261188f565b6111dc6118b9565b6001600160a01b0382166117d0576117b18361188f565b6117d98361188f565b6111dc8261188f565b815460009081036117f5575060006104b7565b82546000905b8082101561184257600061180f83836118c9565b6000878152602090209091508590820154111561182e5780915061183c565b611839816001611d84565b92505b506117fb565b60008211801561186e57508361186b8661185d600186611d71565b600091825260209091200190565b54145b156118875761187e600183611d71565b925050506104b7565b5090506104b7565b6001600160a01b0381166000908152606560205260409020610787906118b483610b1f565b6118e4565b6118c760666118b460355490565b565b60006118d86002848418611fc1565b6108ea90848416611d84565b60006118ef60cd5490565b9050806118fb8461192f565b10156111dc578254600180820185556000858152602080822090930193909355938401805494850181558252902090910155565b8054600090810361194257506000919050565b8154829061195290600190611d71565b8154811061196257611962611de5565b90600052602060002001549050919050565b6000815180845260005b8181101561199a5760208185018101518683018201520161197e565b506000602082860101526020601f19601f83011685010191505092915050565b6020815260006108ea6020830184611974565b6001600160a01b038116811461078757600080fd5b600080604083850312156119f557600080fd5b8235611a00816119cd565b946020939093013593505050565b6000808284036080811215611a2257600080fd5b833592506060601f1982011215611a3857600080fd5b506020830190509250929050565b600080600060608486031215611a5b57600080fd5b8335611a66816119cd565b92506020840135611a76816119cd565b929592945050506040919091013590565b600060208284031215611a9957600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b6040805190810167ffffffffffffffff81118282101715611ad957611ad9611aa0565b60405290565b604051601f8201601f1916810167ffffffffffffffff81118282101715611b0857611b08611aa0565b604052919050565b600080600080600060a08688031215611b2857600080fd5b8535611b33816119cd565b9450602086810135611b44816119cd565b9450604087810135611b55816119cd565b945060608801359350608088013567ffffffffffffffff80821115611b7957600080fd5b818a0191508a601f830112611b8d57600080fd5b813581811115611b9f57611b9f611aa0565b611bad858260051b01611adf565b818152858101925060069190911b83018501908c821115611bcd57600080fd5b928501925b81841015611c165784848e031215611bea5760008081fd5b611bf2611ab6565b8435611bfd816119cd565b8152848701358782015283529284019291850191611bd2565b8096505050505050509295509295909350565b600060208284031215611c3b57600080fd5b81356108ea816119cd565b60008060408385031215611c5957600080fd5b8235611c64816119cd565b91506020830135611c74816119cd565b809150509250929050565b60008060008060608587031215611c9557600080fd5b843593506020850135611ca7816119cd565b9250604085013567ffffffffffffffff80821115611cc457600080fd5b818701915087601f830112611cd857600080fd5b813581811115611ce757600080fd5b886020828501011115611cf957600080fd5b95989497505060200194505050565b600181811c90821680611d1c57607f821691505b602082108103611d3c57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b600060018201611d6a57611d6a611d42565b5060010190565b818103818111156104b7576104b7611d42565b808201808211156104b7576104b7611d42565b634e487b7160e01b600052601260045260246000fd5b600082611dbc57611dbc611d97565b500690565b6001600160a01b038316815260406020820181905260009061137790830184611974565b634e487b7160e01b600052603260045260246000fd5b60008085851115611e0b57600080fd5b83861115611e1857600080fd5b5050820193919092039150565b803560208310156104b757600019602084900360031b1b1692915050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b600081611e9d57611e9d611d42565b506000190190565b634e487b7160e01b600052600160045260246000fd5b601f8211156111dc57600081815260208120601f850160051c81016020861015611ee25750805b601f850160051c820191505b81811015610b1757828155600101611eee565b815167ffffffffffffffff811115611f1b57611f1b611aa0565b611f2f81611f298454611d08565b84611ebb565b602080601f831160018114611f645760008415611f4c5750858301515b600019600386901b1c1916600185901b178555610b17565b600085815260208120601f198616915b82811015611f9357888601518255948401946001909101908401611f74565b5085821015611fb15787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b600082611fd057611fd0611d97565b50049056feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220ceae916e2fad24f9aaa4340b6994418d32d33c6ae1f266c50dd4ec5ccbdbce2764736f6c63430008130033",
+ "balance": "0x0"
+ },
+ "0xFE5E166BA5EA50c04fCa00b07b59966E6C2E9570": {
+ "balance": "0xd3c21bcecceda1000000"
+ }
+ },
+ "number": "0x0",
+ "gasUsed": "0x70000",
+ "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "baseFee": "0x0",
+ "baseFeeEM": "0x0"
+ },
+ "params": {
+ "forks": {
+ "homestead": 0,
+ "byzantium": 0,
+ "constantinople": 0,
+ "petersburg": 0,
+ "istanbul": 0,
+ "EIP150": 0,
+ "EIP158": 0,
+ "EIP155": 0
+ },
+ "chainID": 0,
+ "engine": {
+ "polybft": {
+ "initialValidatorSet": [
+ {
+ "address": "0x61324166B0202DB1E7502924326262274Fa4358F",
+ "blsKey": "06d8d9e6af67c28e85ac400b72c2e635e83234f8a380865e050a206554049a222c4792120d84977a6ca669df56ff3a1cf1cfeccddb650e7aacff4ed6c1d4e37b055858209f80117b3c0a6e7a28e456d4caf2270f430f9df2ba37221f23e9bbd313c9ef488e1849cc5c40d18284d019dde5ed86770309b9c24b70ceff6167a6ca",
+ "balance": "0xf4240",
+ "stake": "0xd3c21bcecceda1000000",
+ "multiAddr": "/ip4/127.0.0.1/tcp/30301/p2p/16Uiu2HAmMYyzK7c649Tnn6XdqFLP7fpPB2QWdck1Ee9vj5a7Nhg8"
+ },
+ {
+ "address": "0xFE5E166BA5EA50c04fCa00b07b59966E6C2E9570",
+ "blsKey": "0601da8856a6d3d3bb0f3bcbb90ea7b8c0db8271b9203e6123c6804aa3fc5f810be33287968ca1af2be11839516850a6ffef2337d99e679b7531efbbea2e3bf727a053c0cbede71da3d5f489b6ad862ccd8bb0bfb7fa379e3395d3b1142594a73020e87d63c298a3a4eba0ace65727f8659bab6389b9448b72512db72bbe937f",
+ "balance": "0xd3c21bcecceda1000000",
+ "stake": "0xd3c21bcecceda1000000",
+ "multiAddr": "/ip4/127.0.0.1/tcp/30302/p2p/16Uiu2HAmLXVapjR2Yx3B1taCmHnckQ1ph2xrawBjW2kvSErps9CX"
+ },
+ {
+ "address": "0x9aBb8441A12d4FD8D505C3fc50cDdc45E0df2b1e",
+ "blsKey": "17c26d9d91dddc3c1318b20a1ddb3322ea1f4e4415c27e9011d706e7407eed672837173d1909cbff6ccdfd110af3b18bdfea878e8120fdb5bae70dc7a044a2f40aa8f118b41704896f474f80fff52d9047fa8e4a464ac86f9d05a0220975d8440e20c6307d866137053cabd4baf6ba84bfa4a22f5f9297c1bfc2380c23535210",
+ "balance": "0xd3c21bcecceda1000000",
+ "stake": "0xd3c21bcecceda1000000",
+ "multiAddr": "/ip4/127.0.0.1/tcp/30303/p2p/16Uiu2HAmGskf5sZ514Ab4SHTPuw8RRBQudyrU211wn3P1knRz9Ed"
+ },
+ {
+ "address": "0xCaB5AAC79Bebe326e0c80d72b5662E73f5D8ea56",
+ "blsKey": "1d7bb7d44a2f0ebeae2f4380f88188080de34635d78a36647f0704c7b70de7291e2e3b9a1ef699a078c6cd9bb816ea2917c2c2fc699c6248f1f7812a167caf7e15361ec16df56d194768d57c79897c681c96f4321651464f7b577d08083d8b67213a1e29dc8495d8389e6cbd85fdd738c402a1801198b57b302e0e00dfaf1247",
+ "balance": "0xd3c21bcecceda1000000",
+ "stake": "0xd3c21bcecceda1000000",
+ "multiAddr": "/ip4/127.0.0.1/tcp/30304/p2p/16Uiu2HAm42EFMhJPGcMRFHPaWWxBzoEsWRbGxJnBHMu4VFojg99U"
+ }
+ ],
+ "bridge": null,
+ "epochSize": 10,
+ "epochReward": 1,
+ "sprintSize": 5,
+ "blockTime": "2s",
+ "governance": "0x61324166B0202DB1E7502924326262274Fa4358F",
+ "mintableNative": false,
+ "nativeTokenConfig": {
+ "name": "Polygon",
+ "symbol": "MATIC",
+ "decimals": 18
+ },
+ "initialTrieRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "maxValidatorSetSize": 9007199254740990,
+ "rewardConfig": {
+ "rewardTokenAddress": "0x0000000000000000000000000000000000001010",
+ "rewardWalletAddress": "0x61324166B0202DB1E7502924326262274Fa4358F",
+ "rewardWalletAmount": "0xf4240"
+ }
+ }
+ },
+ "blockGasTarget": 0,
+ "transactionsAllowList": {
+ "adminAddresses": [
+ "0x061324166B0202Db1E7502924326262274fa4358"
+ ],
+ "enabledAddresses": [
+ "0x061324166B0202Db1E7502924326262274fa4358"
+ ]
+ },
+ "burnContract": null
+ },
+ "bootnodes": [
+ "/ip4/127.0.0.1/tcp/30301/p2p/16Uiu2HAmMYyzK7c649Tnn6XdqFLP7fpPB2QWdck1Ee9vj5a7Nhg8",
+ "/ip4/127.0.0.1/tcp/30302/p2p/16Uiu2HAmLXVapjR2Yx3B1taCmHnckQ1ph2xrawBjW2kvSErps9CX",
+ "/ip4/127.0.0.1/tcp/30303/p2p/16Uiu2HAmGskf5sZ514Ab4SHTPuw8RRBQudyrU211wn3P1knRz9Ed",
+ "/ip4/127.0.0.1/tcp/30304/p2p/16Uiu2HAm42EFMhJPGcMRFHPaWWxBzoEsWRbGxJnBHMu4VFojg99U"
+ ]
+}
\ No newline at end of file
diff --git a/apps/explorer/test/support/fixture/smart_contract/ERC677.sol b/apps/explorer/test/support/fixture/smart_contract/ERC677.sol
index e999f2b306c5..f1b4cf495f76 100644
--- a/apps/explorer/test/support/fixture/smart_contract/ERC677.sol
+++ b/apps/explorer/test/support/fixture/smart_contract/ERC677.sol
@@ -64,7 +64,7 @@ interface IERC677MultiBridgeToken {
* specific functions.
*
* This module is used through inheritance. It will make available the modifier
-* `onlyOwner`, which can be aplied to your functions to restrict their use to
+* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
contract Ownable {
@@ -1160,4 +1160,4 @@ contract ERC677MultiBridgeToken is IERC677MultiBridgeToken, ERC677BridgeToken {
function isBridge(address _address) public view returns (bool) {
return _address != F_ADDR && bridgePointers[_address] != address(0);
}
-}
\ No newline at end of file
+}
diff --git a/apps/explorer/test/support/fixture/smart_contract/issue_3082.sol b/apps/explorer/test/support/fixture/smart_contract/issue_3082.sol
index 7a06a2bfb48b..b0040d68a1a8 100644
--- a/apps/explorer/test/support/fixture/smart_contract/issue_3082.sol
+++ b/apps/explorer/test/support/fixture/smart_contract/issue_3082.sol
@@ -28,7 +28,7 @@ interface IERC677MultiBridgeToken {
* specific functions.
*
* This module is used through inheritance. It will make available the modifier
- * `onlyOwner`, which can be aplied to your functions to restrict their use to
+ * `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
contract Ownable {
@@ -591,4 +591,4 @@ contract Distribution is Ownable, IDistribution {
revert("invalid address");
}
}
-}
\ No newline at end of file
+}
diff --git a/apps/explorer/test/support/fixture/smart_contract/issue_4758.sol b/apps/explorer/test/support/fixture/smart_contract/issue_4758.sol
index 7c56446e505d..75c7dd5312b8 100644
--- a/apps/explorer/test/support/fixture/smart_contract/issue_4758.sol
+++ b/apps/explorer/test/support/fixture/smart_contract/issue_4758.sol
@@ -294,7 +294,7 @@ interface IERC721Enumerable is IERC721 {
*
* Use this as follows (registryAddress is the address of the ENS registry to use):
* -----
- * // This hex value is caclulated by namehash('addr.reverse')
+ * // This hex value is calculated by namehash('addr.reverse')
* bytes32 public constant ENS_ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2;
* function registerReverseENS(address registryAddress, string memory calldata) external {
* require(registryAddress != address(0), "need a valid registry");
diff --git a/apps/explorer/test/support/fixture/smart_contract/issue_5114.sol b/apps/explorer/test/support/fixture/smart_contract/issue_5114.sol
index 942bf82fbd21..5a997e254e0f 100644
--- a/apps/explorer/test/support/fixture/smart_contract/issue_5114.sol
+++ b/apps/explorer/test/support/fixture/smart_contract/issue_5114.sol
@@ -16,7 +16,7 @@ abstract contract Proxy {
/**
* @dev Delegates the current call to `implementation`.
*
- * This function does not return to its internall call site, it will return directly to the external caller.
+ * This function does not return to its internal call site, it will return directly to the external caller.
*/
function _delegate(address implementation) internal virtual {
// solhint-disable-next-line no-inline-assembly
@@ -41,7 +41,7 @@ abstract contract Proxy {
}
/**
- * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function
+ * @dev This is a virtual function that should be overridden so it returns the address to which the fallback function
* and {_fallback} should delegate.
*/
function _implementation() internal view virtual returns (address);
@@ -49,7 +49,7 @@ abstract contract Proxy {
/**
* @dev Delegates the current call to the address returned by `_implementation()`.
*
- * This function does not return to its internall call site, it will return directly to the external caller.
+ * This function does not return to its internal call site, it will return directly to the external caller.
*/
function _fallback() internal virtual {
_beforeFallback();
@@ -76,7 +76,7 @@ abstract contract Proxy {
* @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
* call, or as part of the Solidity `fallback` or `receive` functions.
*
- * If overriden should call `super._beforeFallback()`.
+ * If overridden should call `super._beforeFallback()`.
*/
function _beforeFallback() internal virtual {
}
diff --git a/apps/explorer/test/support/fixture/smart_contract/issue_with_constructor_args.sol b/apps/explorer/test/support/fixture/smart_contract/issue_with_constructor_args.sol
index 58ab19969526..a561dfc1b739 100644
--- a/apps/explorer/test/support/fixture/smart_contract/issue_with_constructor_args.sol
+++ b/apps/explorer/test/support/fixture/smart_contract/issue_with_constructor_args.sol
@@ -292,7 +292,7 @@ abstract contract Proxy {
/**
* @dev Delegates the current call to `implementation`.
*
- * This function does not return to its internall call site, it will return directly to the external caller.
+ * This function does not return to its internal call site, it will return directly to the external caller.
*/
function _delegate(address implementation) internal virtual {
// solhint-disable-next-line no-inline-assembly
@@ -317,7 +317,7 @@ abstract contract Proxy {
}
/**
- * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function
+ * @dev This is a virtual function that should be overridden so it returns the address to which the fallback function
* and {_fallback} should delegate.
*/
function _implementation() internal view virtual returns (address);
@@ -325,7 +325,7 @@ abstract contract Proxy {
/**
* @dev Delegates the current call to the address returned by `_implementation()`.
*
- * This function does not return to its internall call site, it will return directly to the external caller.
+ * This function does not return to its internal call site, it will return directly to the external caller.
*/
function _fallback() internal virtual {
_beforeFallback();
@@ -352,7 +352,7 @@ abstract contract Proxy {
* @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
* call, or as part of the Solidity `fallback` or `receive` functions.
*
- * If overriden should call `super._beforeFallback()`.
+ * If overridden should call `super._beforeFallback()`.
*/
function _beforeFallback() internal virtual {
}
diff --git a/apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol b/apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol
index 6b147f21d86e..2b42daeb972d 100644
--- a/apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol
+++ b/apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol
@@ -113,7 +113,7 @@ interface IHomeWork {
* @param key bytes32 The unique value used to derive the home address.
* @param owner address The account that will be granted ownership of the
* ERC721 token.
- * @dev In order to mint an ERC721 token, the assocated home address cannot be
+ * @dev In order to mint an ERC721 token, the associated home address cannot be
* in use, or else the token will not be able to deploy to the home address.
* The controller is set to this contract until the token is redeemed, at
* which point the redeemer designates a new controller for the home address.
@@ -239,7 +239,7 @@ interface IHomeWork {
* @param owner address The account that will be granted ownership of the
* ERC721 token.
* @return The derived key.
- * @dev In order to mint an ERC721 token, the assocated home address cannot be
+ * @dev In order to mint an ERC721 token, the associated home address cannot be
* in use, or else the token will not be able to deploy to the home address.
* The controller is set to this contract until the token is redeemed, at
* which point the redeemer designates a new controller for the home address.
@@ -1778,7 +1778,7 @@ contract HomeWork is IHomeWork, ERC721Enumerable, IERC721Metadata, IERC1412 {
* @param key bytes32 The unique value used to derive the home address.
* @param owner address The account that will be granted ownership of the
* ERC721 token.
- * @dev In order to mint an ERC721 token, the assocated home address cannot be
+ * @dev In order to mint an ERC721 token, the associated home address cannot be
* in use, or else the token will not be able to deploy to the home address.
* The controller is set to this contract until the token is redeemed, at
* which point the redeemer designates a new controller for the home address.
@@ -2011,7 +2011,7 @@ contract HomeWork is IHomeWork, ERC721Enumerable, IERC721Metadata, IERC1412 {
* @param owner address The account that will be granted ownership of the
* ERC721 token.
* @return The derived key.
- * @dev In order to mint an ERC721 token, the assocated home address cannot be
+ * @dev In order to mint an ERC721 token, the associated home address cannot be
* in use, or else the token will not be able to deploy to the home address.
* The controller is set to this contract until the token is redeemed, at
* which point the redeemer designates a new controller for the home address.
diff --git a/apps/explorer/test/test_helper.exs b/apps/explorer/test/test_helper.exs
index 849fa0b6ae12..938420e729a0 100644
--- a/apps/explorer/test/test_helper.exs
+++ b/apps/explorer/test/test_helper.exs
@@ -13,9 +13,13 @@ ExUnit.start()
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, :auto)
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Account, :auto)
+Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonEdge, :auto)
+Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonZkevm, :auto)
+Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.RSK, :auto)
+Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Suave, :auto)
Mox.defmock(Explorer.ExchangeRates.Source.TestSource, for: Explorer.ExchangeRates.Source)
-Mox.defmock(Explorer.Market.History.Source.TestSource, for: Explorer.Market.History.Source)
+Mox.defmock(Explorer.Market.History.Source.Price.TestSource, for: Explorer.Market.History.Source.Price)
Mox.defmock(Explorer.History.TestHistorian, for: Explorer.History.Historian)
Mox.defmock(EthereumJSONRPC.Mox, for: EthereumJSONRPC.Transport)
diff --git a/apps/indexer/config/dev/arbitrum.exs b/apps/indexer/config/dev/arbitrum.exs
index 18125586af44..e708fed5441b 100644
--- a/apps/indexer/config/dev/arbitrum.exs
+++ b/apps/indexer/config/dev/arbitrum.exs
@@ -19,6 +19,9 @@ config :indexer,
http: EthereumJSONRPC.HTTP.HTTPoison,
url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545",
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
+ method_to_url: [
+ eth_call: ConfigHelper.eth_call_url("http://localhost:8545")
+ ],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
],
variant: EthereumJSONRPC.Arbitrum
diff --git a/apps/indexer/config/dev/besu.exs b/apps/indexer/config/dev/besu.exs
index 9e615bd8ec8f..e09c82159481 100644
--- a/apps/indexer/config/dev/besu.exs
+++ b/apps/indexer/config/dev/besu.exs
@@ -22,9 +22,11 @@ config :indexer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
+ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"),
eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
- trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
+ trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
],
diff --git a/apps/indexer/config/dev/erigon.exs b/apps/indexer/config/dev/erigon.exs
index e764ad6666f5..ef77231c83e5 100644
--- a/apps/indexer/config/dev/erigon.exs
+++ b/apps/indexer/config/dev/erigon.exs
@@ -22,9 +22,11 @@ config :indexer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
+ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"),
eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
- trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
+ trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
],
diff --git a/apps/indexer/config/dev/ganache.exs b/apps/indexer/config/dev/ganache.exs
index be2cd745b191..95ed0de84d89 100644
--- a/apps/indexer/config/dev/ganache.exs
+++ b/apps/indexer/config/dev/ganache.exs
@@ -19,6 +19,9 @@ config :indexer,
http: EthereumJSONRPC.HTTP.HTTPoison,
url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:7545",
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
+ method_to_url: [
+ eth_call: ConfigHelper.eth_call_url("http://localhost:7545")
+ ],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
],
variant: EthereumJSONRPC.Ganache
diff --git a/apps/indexer/config/dev/geth.exs b/apps/indexer/config/dev/geth.exs
index 87b9dbe90613..b7eb4d7facf0 100644
--- a/apps/indexer/config/dev/geth.exs
+++ b/apps/indexer/config/dev/geth.exs
@@ -21,6 +21,7 @@ config :indexer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
+ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"),
debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
diff --git a/apps/indexer/config/dev/nethermind.exs b/apps/indexer/config/dev/nethermind.exs
index aae43f57d827..d97935eb14da 100644
--- a/apps/indexer/config/dev/nethermind.exs
+++ b/apps/indexer/config/dev/nethermind.exs
@@ -22,9 +22,11 @@ config :indexer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
+ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"),
eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
- trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
+ trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
],
diff --git a/apps/indexer/config/dev/rsk.exs b/apps/indexer/config/dev/rsk.exs
index 6e9697be055b..b609f78a224c 100644
--- a/apps/indexer/config/dev/rsk.exs
+++ b/apps/indexer/config/dev/rsk.exs
@@ -23,9 +23,11 @@ config :indexer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
+ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"),
eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
- trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
+ trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
],
diff --git a/apps/indexer/config/prod/arbitrum.exs b/apps/indexer/config/prod/arbitrum.exs
index cef49883b46e..6cf72e206a26 100644
--- a/apps/indexer/config/prod/arbitrum.exs
+++ b/apps/indexer/config/prod/arbitrum.exs
@@ -19,6 +19,9 @@ config :indexer,
http: EthereumJSONRPC.HTTP.HTTPoison,
url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545",
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
+ method_to_url: [
+ eth_call: ConfigHelper.eth_call_url("http://localhost:8545")
+ ],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
],
variant: EthereumJSONRPC.Arbitrum
diff --git a/apps/indexer/config/prod/besu.exs b/apps/indexer/config/prod/besu.exs
index 184f80b408c0..576bdcef3bcf 100644
--- a/apps/indexer/config/prod/besu.exs
+++ b/apps/indexer/config/prod/besu.exs
@@ -21,8 +21,10 @@ config :indexer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
+ eth_call: ConfigHelper.eth_call_url(),
eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL")
],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
diff --git a/apps/indexer/config/prod/erigon.exs b/apps/indexer/config/prod/erigon.exs
index b608c36cd1f7..84a5c6250390 100644
--- a/apps/indexer/config/prod/erigon.exs
+++ b/apps/indexer/config/prod/erigon.exs
@@ -21,8 +21,10 @@ config :indexer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
+ eth_call: ConfigHelper.eth_call_url(),
eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL")
],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
diff --git a/apps/indexer/config/prod/ganache.exs b/apps/indexer/config/prod/ganache.exs
index be2cd745b191..95ed0de84d89 100644
--- a/apps/indexer/config/prod/ganache.exs
+++ b/apps/indexer/config/prod/ganache.exs
@@ -19,6 +19,9 @@ config :indexer,
http: EthereumJSONRPC.HTTP.HTTPoison,
url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:7545",
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
+ method_to_url: [
+ eth_call: ConfigHelper.eth_call_url("http://localhost:7545")
+ ],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
],
variant: EthereumJSONRPC.Ganache
diff --git a/apps/indexer/config/prod/geth.exs b/apps/indexer/config/prod/geth.exs
index f0b57f9b902f..59bb2c23e1f4 100644
--- a/apps/indexer/config/prod/geth.exs
+++ b/apps/indexer/config/prod/geth.exs
@@ -21,6 +21,7 @@ config :indexer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
+ eth_call: ConfigHelper.eth_call_url(),
debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL")
],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
diff --git a/apps/indexer/config/prod/nethermind.exs b/apps/indexer/config/prod/nethermind.exs
index 04ff07c88d72..2dc6c0f98e80 100644
--- a/apps/indexer/config/prod/nethermind.exs
+++ b/apps/indexer/config/prod/nethermind.exs
@@ -21,8 +21,10 @@ config :indexer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
+ eth_call: ConfigHelper.eth_call_url(),
eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL")
],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
diff --git a/apps/indexer/config/prod/rsk.exs b/apps/indexer/config/prod/rsk.exs
index 4b0a388ddbe9..444eff26e653 100644
--- a/apps/indexer/config/prod/rsk.exs
+++ b/apps/indexer/config/prod/rsk.exs
@@ -23,8 +23,10 @@ config :indexer,
fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"),
fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"),
method_to_url: [
+ eth_call: ConfigHelper.eth_call_url(),
eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL")
],
http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts]
diff --git a/apps/indexer/lib/indexer/application.ex b/apps/indexer/lib/indexer/application.ex
index e47f5deeed23..9984918e06f9 100644
--- a/apps/indexer/lib/indexer/application.ex
+++ b/apps/indexer/lib/indexer/application.ex
@@ -24,7 +24,13 @@ defmodule Indexer.Application do
json_rpc_named_arguments = Application.fetch_env!(:indexer, :json_rpc_named_arguments)
+ pool_size =
+ Application.get_env(:indexer, Indexer.Fetcher.TokenInstance.Retry)[:concurrency] +
+ Application.get_env(:indexer, Indexer.Fetcher.TokenInstance.Realtime)[:concurrency] +
+ Application.get_env(:indexer, Indexer.Fetcher.TokenInstance.Sanitize)[:concurrency]
+
base_children = [
+ :hackney_pool.child_spec(:token_instance_fetcher, max_connections: pool_size),
{Memory.Monitor, [memory_monitor_options, [name: memory_monitor_name]]},
{CoinBalanceOnDemand.Supervisor, [json_rpc_named_arguments]},
{TokenTotalSupplyOnDemand.Supervisor, []},
diff --git a/apps/indexer/lib/indexer/block/catchup/fetcher.ex b/apps/indexer/lib/indexer/block/catchup/fetcher.ex
index faa0654ab785..3b5fcbbc31fa 100644
--- a/apps/indexer/lib/indexer/block/catchup/fetcher.ex
+++ b/apps/indexer/lib/indexer/block/catchup/fetcher.ex
@@ -55,7 +55,9 @@ defmodule Indexer.Block.Catchup.Fetcher do
shrunk: false
}
- missing_ranges ->
+ latest_missing_ranges ->
+ missing_ranges = filter_consensus_blocks(latest_missing_ranges)
+
first.._ = List.first(missing_ranges)
_..last = List.last(missing_ranges)
@@ -85,6 +87,21 @@ defmodule Indexer.Block.Catchup.Fetcher do
end
end
+ defp filter_consensus_blocks(ranges) do
+ filtered_ranges =
+ ranges
+ |> Enum.map(&Chain.missing_block_number_ranges(&1))
+ |> List.flatten()
+
+ consensus_blocks = ranges_to_numbers(ranges) -- ranges_to_numbers(filtered_ranges)
+
+ consensus_blocks
+ |> numbers_to_ranges()
+ |> MissingRangesManipulator.clear_batch()
+
+ filtered_ranges
+ end
+
@doc """
The number of blocks to request in one call to the JSONRPC. Defaults to
10. Block requests also include the transactions for those blocks. *These transactions
@@ -230,9 +247,6 @@ defmodule Indexer.Block.Catchup.Fetcher do
rescue
exception ->
Logger.error(fn -> [Exception.format(:error, exception, __STACKTRACE__), ?\n, ?\n, "Retrying."] end)
-
- push_back(sequence, range)
-
{:error, exception}
end
@@ -289,14 +303,14 @@ defmodule Indexer.Block.Catchup.Fetcher do
defp numbers_to_ranges(numbers) when is_list(numbers) do
numbers
- |> Enum.sort()
+ |> Enum.sort(&>=/2)
|> Enum.chunk_while(
nil,
fn
number, nil ->
{:cont, number..number}
- number, first..last when number == last + 1 ->
+ number, first..last when number == last - 1 ->
{:cont, first..number}
number, range ->
@@ -306,6 +320,12 @@ defmodule Indexer.Block.Catchup.Fetcher do
)
end
+ defp ranges_to_numbers(ranges) do
+ ranges
+ |> Enum.map(&Enum.to_list/1)
+ |> List.flatten()
+ end
+
defp put_memory_monitor(sequence_options, %__MODULE__{memory_monitor: nil}) when is_list(sequence_options),
do: sequence_options
diff --git a/apps/indexer/lib/indexer/block/catchup/helper.ex b/apps/indexer/lib/indexer/block/catchup/helper.ex
deleted file mode 100644
index 951ed3548a46..000000000000
--- a/apps/indexer/lib/indexer/block/catchup/helper.ex
+++ /dev/null
@@ -1,39 +0,0 @@
-defmodule Indexer.Block.Catchup.Helper do
- @moduledoc """
- Catchup helper functions
- """
-
- def sanitize_ranges(ranges) do
- ranges
- |> Enum.filter(&(not is_nil(&1)))
- |> Enum.sort_by(
- fn
- from.._to -> from
- el -> el
- end,
- :asc
- )
- |> Enum.chunk_while(
- nil,
- fn
- _from.._to = chunk, nil ->
- {:cont, chunk}
-
- _ch_from..ch_to = chunk, acc_from..acc_to = acc ->
- if Range.disjoint?(chunk, acc),
- do: {:cont, acc, chunk},
- else: {:cont, acc_from..max(ch_to, acc_to)}
-
- num, nil ->
- {:halt, num}
-
- num, acc_from.._ = acc ->
- if Range.disjoint?(num..num, acc), do: {:cont, acc, num}, else: {:halt, acc_from}
-
- _, num ->
- {:halt, num}
- end,
- fn reminder -> {:cont, reminder, nil} end
- )
- end
-end
diff --git a/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex b/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex
index 0d4c6bfaeacc..9c776610ae81 100644
--- a/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex
+++ b/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex
@@ -5,11 +5,10 @@ defmodule Indexer.Block.Catchup.MissingRangesCollector do
use GenServer
- alias Explorer.{Chain, Repo}
+ alias EthereumJSONRPC.Utility.RangesHelper
+ alias Explorer.{Chain, Helper, Repo}
alias Explorer.Chain.Cache.BlockNumber
- alias Explorer.Helper, as: ExplorerHelper
alias Explorer.Utility.{MissingBlockRange, MissingRangesManipulator}
- alias Indexer.Block.Catchup.Helper
@default_missing_ranges_batch_size 100_000
@future_check_interval Application.compile_env(:indexer, __MODULE__)[:future_check_interval]
@@ -22,7 +21,12 @@ defmodule Indexer.Block.Catchup.MissingRangesCollector do
@impl true
def init(_) do
- {:ok, define_init()}
+ {:ok, %{min_fetched_block_number: nil, max_fetched_block_number: nil}, {:continue, :ok}}
+ end
+
+ @impl true
+ def handle_continue(:ok, _state) do
+ {:noreply, define_init()}
end
defp define_init do
@@ -177,27 +181,19 @@ defmodule Indexer.Block.Catchup.MissingRangesCollector do
end
defp first_block do
- string_value = Application.get_env(:indexer, :first_block)
-
- case Integer.parse(string_value) do
- {integer, ""} ->
- integer
+ first_block_from_config = Application.get_env(:indexer, :first_block)
- _ ->
- min_missing_block_number =
- "min_missing_block_number"
- |> Chain.get_last_fetched_counter()
- |> Decimal.to_integer()
+ min_missing_block_number =
+ "min_missing_block_number"
+ |> Chain.get_last_fetched_counter()
+ |> Decimal.to_integer()
- min_missing_block_number
- end
+ max(first_block_from_config, min_missing_block_number)
end
defp last_block do
- case Integer.parse(Application.get_env(:indexer, :last_block)) do
- {block, ""} -> block + 1
- _ -> fetch_max_block_number()
- end
+ last_block = Application.get_env(:indexer, :last_block)
+ if last_block, do: last_block + 1, else: fetch_max_block_number()
end
defp fetch_max_block_number do
@@ -216,9 +212,12 @@ defmodule Indexer.Block.Catchup.MissingRangesCollector do
end
defp continue_future_updating?(max_fetched_block_number) do
- case Integer.parse(Application.get_env(:indexer, :last_block)) do
- {block, ""} -> max_fetched_block_number < block
- _ -> true
+ last_block = Application.get_env(:indexer, :last_block)
+
+ if last_block do
+ max_fetched_block_number < last_block
+ else
+ true
end
end
@@ -233,7 +232,7 @@ defmodule Indexer.Block.Catchup.MissingRangesCollector do
|> Enum.map(fn string_range ->
case String.split(string_range, "..") do
[from_string, "latest"] ->
- ExplorerHelper.parse_integer(from_string)
+ Helper.parse_integer(from_string)
[from_string, to_string] ->
get_from_to(from_string, to_string)
@@ -242,7 +241,7 @@ defmodule Indexer.Block.Catchup.MissingRangesCollector do
nil
end
end)
- |> Helper.sanitize_ranges()
+ |> RangesHelper.sanitize_ranges()
case List.last(ranges) do
_from.._to ->
diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex
index 27d5c079e8a1..11261231247d 100644
--- a/apps/indexer/lib/indexer/block/fetcher.ex
+++ b/apps/indexer/lib/indexer/block/fetcher.ex
@@ -29,18 +29,20 @@ defmodule Indexer.Block.Fetcher do
UncleBlock
}
- alias Indexer.{Prometheus, Tracer}
+ alias Indexer.{Prometheus, TokenBalances, Tracer}
alias Indexer.Transform.{
AddressCoinBalances,
- AddressCoinBalancesDaily,
Addresses,
AddressTokenBalances,
MintTransfers,
+ TokenInstances,
TokenTransfers,
TransactionActions
}
+ alias Indexer.Transform.PolygonEdge.{DepositExecutes, Withdrawals}
+
alias Indexer.Transform.Blocks, as: TransformBlocks
@type address_hash_to_fetched_balance_block_number :: %{String.t() => Block.block_number()}
@@ -141,6 +143,13 @@ defmodule Indexer.Block.Fetcher do
%{token_transfers: token_transfers, tokens: tokens} = TokenTransfers.parse(logs),
%{transaction_actions: transaction_actions} = TransactionActions.parse(logs),
%{mint_transfers: mint_transfers} = MintTransfers.parse(logs),
+ polygon_edge_withdrawals =
+ if(callback_module == Indexer.Block.Realtime.Fetcher, do: Withdrawals.parse(logs), else: []),
+ polygon_edge_deposit_executes =
+ if(callback_module == Indexer.Block.Realtime.Fetcher,
+ do: DepositExecutes.parse(logs),
+ else: []
+ ),
%FetchedBeneficiaries{params_set: beneficiary_params_set, errors: beneficiaries_errors} =
fetch_beneficiaries(blocks, transactions_with_receipts, json_rpc_named_arguments),
addresses =
@@ -163,36 +172,49 @@ defmodule Indexer.Block.Fetcher do
withdrawals: withdrawals_params
}
|> AddressCoinBalances.params_set(),
- coin_balances_params_daily_set =
- %{
- coin_balances_params: coin_balances_params_set,
- blocks: blocks
- }
- |> AddressCoinBalancesDaily.params_set(),
beneficiaries_with_gas_payment =
beneficiaries_with_gas_payment(blocks, beneficiary_params_set, transactions_with_receipts),
address_token_balances = AddressTokenBalances.params_set(%{token_transfers_params: token_transfers}),
transaction_actions =
Enum.map(transaction_actions, fn action -> Map.put(action, :data, Map.delete(action.data, :block_number)) end),
+ token_instances = TokenInstances.params_set(%{token_transfers_params: token_transfers}),
+ basic_import_options = %{
+ addresses: %{params: addresses},
+ address_coin_balances: %{params: coin_balances_params_set},
+ address_token_balances: %{params: address_token_balances},
+ address_current_token_balances: %{
+ params: address_token_balances |> MapSet.to_list() |> TokenBalances.to_address_current_token_balances()
+ },
+ blocks: %{params: blocks},
+ block_second_degree_relations: %{params: block_second_degree_relations_params},
+ block_rewards: %{errors: beneficiaries_errors, params: beneficiaries_with_gas_payment},
+ logs: %{params: logs},
+ token_transfers: %{params: token_transfers},
+ tokens: %{params: tokens},
+ transactions: %{params: transactions_with_receipts},
+ withdrawals: %{params: withdrawals_params},
+ token_instances: %{params: token_instances}
+ },
+ import_options =
+ (if Application.get_env(:explorer, :chain_type) == "polygon_edge" do
+ basic_import_options
+ |> Map.put_new(:polygon_edge_withdrawals, %{params: polygon_edge_withdrawals})
+ |> Map.put_new(:polygon_edge_deposit_executes, %{params: polygon_edge_deposit_executes})
+ else
+ basic_import_options
+ end),
{:ok, inserted} <-
__MODULE__.import(
state,
- %{
- addresses: %{params: addresses},
- address_coin_balances: %{params: coin_balances_params_set},
- address_coin_balances_daily: %{params: coin_balances_params_daily_set},
- address_token_balances: %{params: address_token_balances},
- blocks: %{params: blocks},
- block_second_degree_relations: %{params: block_second_degree_relations_params},
- block_rewards: %{errors: beneficiaries_errors, params: beneficiaries_with_gas_payment},
- logs: %{params: logs},
- token_transfers: %{params: token_transfers},
- tokens: %{on_conflict: :nothing, params: tokens},
- transactions: %{params: transactions_with_receipts},
- transaction_actions: %{params: transaction_actions},
- withdrawals: %{params: withdrawals_params}
- }
- ) do
+ import_options
+ ),
+ {:tx_actions, {:ok, inserted_tx_actions}} <-
+ {:tx_actions,
+ Chain.import(%{
+ transaction_actions: %{params: transaction_actions},
+ timeout: :infinity
+ })} do
+ inserted = Map.merge(inserted, inserted_tx_actions)
Prometheus.Instrumenter.block_batch_fetch(fetch_time, callback_module)
result = {:ok, %{inserted: inserted, errors: blocks_errors}}
update_block_cache(inserted[:blocks])
@@ -385,15 +407,15 @@ defmodule Indexer.Block.Fetcher do
def fetch_beneficiaries_manual(block, transactions) do
block
- |> Chain.block_reward_by_parts(transactions)
+ |> Block.block_reward_by_parts(transactions)
|> reward_parts_to_beneficiaries()
end
defp reward_parts_to_beneficiaries(reward_parts) do
reward =
reward_parts.static_reward
- |> Wei.sum(reward_parts.txn_fees)
- |> Wei.sub(reward_parts.burned_fees)
+ |> Wei.sum(reward_parts.transaction_fees)
+ |> Wei.sub(reward_parts.burnt_fees)
|> Wei.sum(reward_parts.uncle_reward)
MapSet.new([
@@ -558,6 +580,7 @@ defmodule Indexer.Block.Fetcher do
hash: hash
} = address_params
) do
- {{hash, fetched_coin_balance_block_number}, Map.delete(address_params, :fetched_coin_balance_block_number)}
+ {{String.downcase(hash), fetched_coin_balance_block_number},
+ Map.delete(address_params, :fetched_coin_balance_block_number)}
end
end
diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex
index 3a1571a6e50c..9f989d017f96 100644
--- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex
+++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex
@@ -30,9 +30,11 @@ defmodule Indexer.Block.Realtime.Fetcher do
alias Explorer.Chain.Cache.Accounts
alias Explorer.Chain.Events.Publisher
alias Explorer.Counters.AverageBlockTime
+ alias Explorer.Utility.MissingRangesManipulator
alias Indexer.{Block, Tracer}
alias Indexer.Block.Realtime.TaskSupervisor
- alias Indexer.Fetcher.CoinBalance
+ alias Indexer.Fetcher.{CoinBalance, CoinBalanceDailyUpdater}
+ alias Indexer.Fetcher.PolygonEdge.{DepositExecute, Withdrawal}
alias Indexer.Prometheus
alias Indexer.Transform.Addresses
alias Timex.Duration
@@ -178,7 +180,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
polling_period =
case AverageBlockTime.average_block_time() do
{:error, :disabled} -> 2_000
- block_time -> round(Duration.to_milliseconds(block_time) / 2)
+ block_time -> min(round(Duration.to_milliseconds(block_time) / 2), 30_000)
end
safe_polling_period = max(polling_period, @minimum_safe_polling_period)
@@ -193,8 +195,6 @@ defmodule Indexer.Block.Realtime.Fetcher do
block_fetcher,
%{
address_coin_balances: %{params: address_coin_balances_params},
- address_coin_balances_daily: %{params: address_coin_balances_daily_params},
- address_hash_to_fetched_balance_block_number: address_hash_to_block_number,
addresses: %{params: addresses_params},
block_rewards: block_rewards
} = options
@@ -208,10 +208,8 @@ defmodule Indexer.Block.Realtime.Fetcher do
}}} <-
{:balances,
balances(block_fetcher, %{
- address_hash_to_block_number: address_hash_to_block_number,
addresses_params: addresses_params,
- balances_params: address_coin_balances_params,
- balances_daily_params: address_coin_balances_daily_params
+ balances_params: address_coin_balances_params
})},
{block_reward_errors, chain_import_block_rewards} = Map.pop(block_rewards, :errors),
chain_import_options =
@@ -220,8 +218,8 @@ defmodule Indexer.Block.Realtime.Fetcher do
|> put_in([:addresses, :params], balances_addresses_params)
|> put_in([:blocks, :params, Access.all(), :consensus], true)
|> put_in([:block_rewards], chain_import_block_rewards)
- |> put_in([Access.key(:address_coin_balances, %{}), :params], balances_params)
- |> put_in([Access.key(:address_coin_balances_daily, %{}), :params], balances_daily_params),
+ |> put_in([Access.key(:address_coin_balances, %{}), :params], balances_params),
+ CoinBalanceDailyUpdater.add_daily_balances_params(balances_daily_params),
{:import, {:ok, imported} = ok} <- {:import, Chain.import(chain_import_options)} do
async_import_remaining_block_data(
imported,
@@ -286,6 +284,9 @@ defmodule Indexer.Block.Realtime.Fetcher do
Indexer.Logger.metadata(
fn ->
if reorg? do
+ # we need to remove all rows from `polygon_edge_withdrawals` and `polygon_edge_deposit_executes` tables previously written starting from reorg block number
+ remove_polygon_edge_assets_by_number(block_number_to_fetch)
+
# give previous fetch attempt (for same block number) a chance to finish
# before fetching again, to reduce block consensus mistakes
:timer.sleep(@reorg_delay)
@@ -298,6 +299,13 @@ defmodule Indexer.Block.Realtime.Fetcher do
)
end
+ defp remove_polygon_edge_assets_by_number(block_number_to_fetch) do
+ if Application.get_env(:explorer, :chain_type) == "polygon_edge" do
+ Withdrawal.remove(block_number_to_fetch)
+ DepositExecute.remove(block_number_to_fetch)
+ end
+ end
+
@decorate span(tracer: Tracer)
defp do_fetch_and_import_block(block_number_to_fetch, block_fetcher, retry) do
time_before = Timex.now()
@@ -310,6 +318,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
case result do
{:ok, %{inserted: inserted, errors: []}} ->
log_import_timings(inserted, fetch_duration, time_before)
+ MissingRangesManipulator.clear_batch([block_number_to_fetch..block_number_to_fetch])
Logger.debug("Fetched and imported.")
{:ok, %{inserted: _, errors: [_ | _] = errors}} ->
@@ -428,7 +437,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
) do
case options
|> fetch_balances_params_list()
- |> EthereumJSONRPC.fetch_balances(json_rpc_named_arguments) do
+ |> EthereumJSONRPC.fetch_balances(json_rpc_named_arguments, CoinBalance.batch_size()) do
{:ok, %FetchedBalances{params_list: params_list, errors: []}} ->
merged_addresses_params =
%{address_coin_balances: params_list}
@@ -463,35 +472,13 @@ defmodule Indexer.Block.Realtime.Fetcher do
end
end
- defp fetch_balances_params_list(%{
- addresses_params: addresses_params,
- address_hash_to_block_number: address_hash_to_block_number,
- balances_params: balances_params
- }) do
- addresses_params
- |> addresses_params_to_fetched_balances_params_set(%{address_hash_to_block_number: address_hash_to_block_number})
- |> MapSet.union(balances_params_to_fetch_balances_params_set(balances_params))
+ defp fetch_balances_params_list(%{balances_params: balances_params}) do
+ balances_params
+ |> balances_params_to_fetch_balances_params_set()
# stable order for easier moxing
|> Enum.sort_by(fn %{hash_data: hash_data, block_quantity: block_quantity} -> {hash_data, block_quantity} end)
end
- defp addresses_params_to_fetched_balances_params_set(addresses_params, %{
- address_hash_to_block_number: address_hash_to_block_number
- }) do
- Enum.into(addresses_params, MapSet.new(), fn %{hash: address_hash} = address_params when is_binary(address_hash) ->
- block_number =
- case address_params do
- %{fetched_coin_balance_block_number: block_number} when is_integer(block_number) ->
- block_number
-
- _ ->
- Map.fetch!(address_hash_to_block_number, address_hash)
- end
-
- %{hash_data: address_hash, block_quantity: integer_to_quantity(block_number)}
- end)
- end
-
defp balances_params_to_fetch_balances_params_set(balances_params) do
Enum.into(balances_params, MapSet.new(), fn %{address_hash: address_hash, block_number: block_number} ->
%{hash_data: address_hash, block_quantity: integer_to_quantity(block_number)}
diff --git a/apps/indexer/lib/indexer/buffered_task.ex b/apps/indexer/lib/indexer/buffered_task.ex
index 7f2565ffe20a..2ef7059f20b2 100644
--- a/apps/indexer/lib/indexer/buffered_task.ex
+++ b/apps/indexer/lib/indexer/buffered_task.ex
@@ -71,7 +71,7 @@ defmodule Indexer.BufferedTask do
flush_interval: nil,
max_batch_size: nil,
max_concurrency: nil,
- poll: false,
+ poll: true,
metadata: [],
current_buffer: [],
bound_queue: %BoundQueue{},
@@ -231,7 +231,7 @@ defmodule Indexer.BufferedTask do
state = %BufferedTask{
callback_module: callback_module,
callback_module_state: Keyword.fetch!(opts, :state),
- poll: Keyword.get(opts, :poll, false),
+ poll: Keyword.get(opts, :poll, true),
task_supervisor: Keyword.fetch!(opts, :task_supervisor),
flush_interval: Keyword.fetch!(opts, :flush_interval),
max_batch_size: Keyword.fetch!(opts, :max_batch_size),
@@ -442,21 +442,8 @@ defmodule Indexer.BufferedTask do
end
# get more work from `init/2`
- defp schedule_next(%BufferedTask{poll: true, bound_queue: %BoundQueue{size: 0}} = state) do
- do_initial_stream(state)
- end
-
- # was shrunk and was out of work, get more work from `init/2`
- defp schedule_next(%BufferedTask{bound_queue: %BoundQueue{size: 0, maximum_size: maximum_size}} = state)
- when maximum_size != nil do
- Logger.info(fn ->
- [
- "BufferedTask ",
- process(self()),
- " ran out of work, but work queue was shrunk to save memory, so restoring lost work from `c:init/2`."
- ]
- end)
-
+ defp schedule_next(%BufferedTask{poll: true, bound_queue: %BoundQueue{size: 0}, task_ref_to_batch: tasks} = state)
+ when tasks == %{} do
do_initial_stream(state)
end
diff --git a/apps/indexer/lib/indexer/fetcher/block_reward.ex b/apps/indexer/lib/indexer/fetcher/block_reward.ex
index a4be4c832b3a..e179538b2ccf 100644
--- a/apps/indexer/lib/indexer/fetcher/block_reward.ex
+++ b/apps/indexer/lib/indexer/fetcher/block_reward.ex
@@ -21,7 +21,7 @@ defmodule Indexer.Fetcher.BlockReward do
alias Indexer.{BufferedTask, Tracer}
alias Indexer.Fetcher.BlockReward.Supervisor, as: BlockRewardSupervisor
alias Indexer.Fetcher.CoinBalance
- alias Indexer.Transform.{AddressCoinBalances, AddressCoinBalancesDaily, Addresses}
+ alias Indexer.Transform.{AddressCoinBalances, Addresses}
@behaviour BufferedTask
@@ -62,9 +62,12 @@ defmodule Indexer.Fetcher.BlockReward do
@impl BufferedTask
def init(initial, reducer, _) do
{:ok, final} =
- Chain.stream_blocks_without_rewards(initial, fn %{number: number}, acc ->
- reducer.(number, acc)
- end)
+ Chain.stream_blocks_without_rewards(
+ initial,
+ fn %{number: number}, acc ->
+ reducer.(number, acc)
+ end
+ )
final
end
@@ -97,7 +100,7 @@ defmodule Indexer.Fetcher.BlockReward do
{:error, reason} ->
Logger.error(
fn ->
- ["failed to fetch: ", inspect(reason)]
+ ["failed to fetch: ", inspect(reason), " hash: ", inspect(hash_string_by_number)]
end,
error_count: consensus_number_count
)
@@ -271,28 +274,9 @@ defmodule Indexer.Fetcher.BlockReward do
addresses_params = Addresses.extract_addresses(%{block_reward_contract_beneficiaries: block_rewards_params})
address_coin_balances_params_set = AddressCoinBalances.params_set(%{beneficiary_params: block_rewards_params})
- address_coin_balances_params_with_block_timestamp =
- block_rewards_params
- |> Enum.map(fn block_rewards_param ->
- %{
- address_hash: block_rewards_param.address_hash,
- block_number: block_rewards_param.block_number,
- block_timestamp: block_rewards_param.block_timestamp
- }
- end)
- |> Enum.into(MapSet.new())
-
- address_coin_balances_params_with_block_timestamp_set = %{
- address_coin_balances_params_with_block_timestamp: address_coin_balances_params_with_block_timestamp
- }
-
- address_coin_balances_daily_params_set =
- AddressCoinBalancesDaily.params_set(address_coin_balances_params_with_block_timestamp_set)
-
Chain.import(%{
addresses: %{params: addresses_params},
address_coin_balances: %{params: address_coin_balances_params_set},
- address_coin_balances_daily: %{params: address_coin_balances_daily_params_set},
block_rewards: %{params: block_rewards_params}
})
end
diff --git a/apps/indexer/lib/indexer/fetcher/coin_balance.ex b/apps/indexer/lib/indexer/fetcher/coin_balance.ex
index de0e03e1f6d7..3d7171724547 100644
--- a/apps/indexer/lib/indexer/fetcher/coin_balance.ex
+++ b/apps/indexer/lib/indexer/fetcher/coin_balance.ex
@@ -11,7 +11,7 @@ defmodule Indexer.Fetcher.CoinBalance do
import EthereumJSONRPC, only: [integer_to_quantity: 1, quantity_to_integer: 1]
- alias EthereumJSONRPC.{Blocks, FetchedBalances}
+ alias EthereumJSONRPC.{Blocks, FetchedBalances, Utility.RangesHelper}
alias Explorer.Chain
alias Explorer.Chain.{Block, Hash}
alias Explorer.Chain.Cache.Accounts
@@ -23,6 +23,8 @@ defmodule Indexer.Fetcher.CoinBalance do
@default_max_batch_size 500
@default_max_concurrency 4
+ def batch_size, do: defaults()[:max_batch_size]
+
@doc """
Asynchronously fetches balances for each address `hash` at the `block_number`.
"""
@@ -61,11 +63,15 @@ defmodule Indexer.Fetcher.CoinBalance do
@impl BufferedTask
def init(initial, reducer, _) do
{:ok, final} =
- Chain.stream_unfetched_balances(initial, fn address_fields, acc ->
- address_fields
- |> entry()
- |> reducer.(acc)
- end)
+ Chain.stream_unfetched_balances(
+ initial,
+ fn address_fields, acc ->
+ address_fields
+ |> entry()
+ |> reducer.(acc)
+ end,
+ true
+ )
final
end
@@ -78,9 +84,7 @@ defmodule Indexer.Fetcher.CoinBalance do
unique_entries = Enum.uniq(entries)
unique_filtered_entries =
- Enum.filter(unique_entries, fn {_hash, block_number} ->
- block_number >= EthereumJSONRPC.first_block_to_fetch(:trace_first_block)
- end)
+ Enum.filter(unique_entries, fn {_hash, block_number} -> RangesHelper.traceable_block_number?(block_number) end)
unique_entry_count = Enum.count(unique_filtered_entries)
Logger.metadata(count: unique_entry_count)
diff --git a/apps/indexer/lib/indexer/fetcher/coin_balance_daily_updater.ex b/apps/indexer/lib/indexer/fetcher/coin_balance_daily_updater.ex
new file mode 100644
index 000000000000..101178630611
--- /dev/null
+++ b/apps/indexer/lib/indexer/fetcher/coin_balance_daily_updater.ex
@@ -0,0 +1,68 @@
+defmodule Indexer.Fetcher.CoinBalanceDailyUpdater do
+ @moduledoc """
+ Accumulates and periodically updates daily coin balances
+ """
+
+ use GenServer
+
+ alias Explorer.Chain
+ alias Explorer.Counters.AverageBlockTime
+ alias Timex.Duration
+
+ @default_update_interval :timer.seconds(10)
+
+ def start_link(_) do
+ GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
+ end
+
+ @impl true
+ def init(_) do
+ schedule_next_update()
+
+ {:ok, %{}}
+ end
+
+ def add_daily_balances_params(daily_balances_params) do
+ GenServer.cast(__MODULE__, {:add_daily_balances_params, daily_balances_params})
+ end
+
+ @impl true
+ def handle_cast({:add_daily_balances_params, daily_balances_params}, state) do
+ {:noreply, Enum.reduce(daily_balances_params, state, &put_new_param/2)}
+ end
+
+ defp put_new_param(%{day: day, address_hash: address_hash, value: value} = param, acc) do
+ Map.update(acc, {address_hash, day}, param, fn %{value: old_value} = old_param ->
+ if is_nil(old_value) or value > old_value, do: param, else: old_param
+ end)
+ end
+
+ @impl true
+ def handle_info(:update, state) when state == %{} do
+ schedule_next_update()
+
+ {:noreply, %{}}
+ end
+
+ def handle_info(:update, state) do
+ Chain.import(%{address_coin_balances_daily: %{params: Map.values(state)}})
+
+ schedule_next_update()
+
+ {:noreply, %{}}
+ end
+
+ def handle_info(_, state) do
+ {:noreply, state}
+ end
+
+ defp schedule_next_update do
+ update_interval =
+ case AverageBlockTime.average_block_time() do
+ {:error, :disabled} -> @default_update_interval
+ block_time -> round(Duration.to_milliseconds(block_time))
+ end
+
+ Process.send_after(self(), :update, update_interval)
+ end
+end
diff --git a/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex b/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex
index f48b889631fb..a4a5f5f97d6f 100644
--- a/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex
+++ b/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex
@@ -15,7 +15,7 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do
alias EthereumJSONRPC.FetchedBalances
alias Explorer.{Chain, Repo}
- alias Explorer.Chain.Address
+ alias Explorer.Chain.{Address, Hash}
alias Explorer.Chain.Address.{CoinBalance, CoinBalanceDaily}
alias Explorer.Chain.Cache.{Accounts, BlockNumber}
alias Explorer.Counters.AverageBlockTime
@@ -50,6 +50,11 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do
end
end
+ @spec trigger_historic_fetch(Hash.Address.t(), non_neg_integer()) :: balance_status
+ def trigger_historic_fetch(address_hash, block_number) do
+ do_trigger_historic_fetch(address_hash, block_number)
+ end
+
## Callbacks
def child_spec([json_rpc_named_arguments, server_opts]) do
@@ -134,6 +139,12 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do
do_trigger_balance_fetch_query(address, latest_block_number, stale_balance_window, latest, latest_by_day)
end
+ defp do_trigger_historic_fetch(address_hash, block_number) do
+ GenServer.cast(__MODULE__, {:fetch_and_import, block_number, %{hash: address_hash}})
+
+ {:stale, 0}
+ end
+
defp do_trigger_balance_fetch_query(
address,
latest_block_number,
diff --git a/apps/indexer/lib/indexer/fetcher/contract_code.ex b/apps/indexer/lib/indexer/fetcher/contract_code.ex
index 908865287bf6..716b8f25e204 100644
--- a/apps/indexer/lib/indexer/fetcher/contract_code.ex
+++ b/apps/indexer/lib/indexer/fetcher/contract_code.ex
@@ -64,7 +64,8 @@ defmodule Indexer.Fetcher.ContractCode do
transaction_fields
|> entry()
|> reducer.(acc)
- end
+ end,
+ true
)
final
@@ -97,6 +98,7 @@ defmodule Indexer.Fetcher.ContractCode do
Logger.debug("fetching created_contract_code for transactions")
entries
+ |> Enum.uniq()
|> Enum.map(¶ms/1)
|> EthereumJSONRPC.fetch_codes(json_rpc_named_arguments)
|> case do
diff --git a/apps/indexer/lib/indexer/fetcher/empty_blocks_sanitizer.ex b/apps/indexer/lib/indexer/fetcher/empty_blocks_sanitizer.ex
index 3f15cdf92a87..8d92ccdc57a8 100644
--- a/apps/indexer/lib/indexer/fetcher/empty_blocks_sanitizer.ex
+++ b/apps/indexer/lib/indexer/fetcher/empty_blocks_sanitizer.ex
@@ -15,6 +15,7 @@ defmodule Indexer.Fetcher.EmptyBlocksSanitizer do
alias Ecto.Changeset
alias Explorer.{Chain, Repo}
alias Explorer.Chain.{Block, PendingBlockOperation, Transaction}
+ alias Explorer.Chain.Cache.BlockNumber
alias Explorer.Chain.Import.Runner.Blocks
@interval :timer.seconds(10)
@@ -41,9 +42,11 @@ defmodule Indexer.Fetcher.EmptyBlocksSanitizer do
@impl GenServer
def init(opts) when is_list(opts) do
+ interval = Application.get_env(:indexer, __MODULE__)[:interval]
+
state = %__MODULE__{
json_rpc_named_arguments: Keyword.fetch!(opts, :json_rpc_named_arguments),
- interval: opts[:interval] || @interval
+ interval: interval || @interval
}
Process.send_after(self(), :sanitize_empty_blocks, state.interval)
@@ -68,19 +71,15 @@ defmodule Indexer.Fetcher.EmptyBlocksSanitizer do
end
defp sanitize_empty_blocks(json_rpc_named_arguments) do
- unprocessed_non_empty_blocks_from_db = unprocessed_non_empty_blocks_query_list(limit())
-
- uniq_block_hashes = unprocessed_non_empty_blocks_from_db
-
- if Enum.count(uniq_block_hashes) > 0 do
- Repo.update_all(
- from(
- block in Block,
- where: block.hash in ^uniq_block_hashes
- ),
- set: [is_empty: false, updated_at: Timex.now()]
- )
- end
+ unprocessed_non_empty_blocks_query = unprocessed_non_empty_blocks_query(limit())
+
+ Repo.update_all(
+ from(
+ block in Block,
+ where: block.hash in subquery(unprocessed_non_empty_blocks_query)
+ ),
+ set: [is_empty: false, updated_at: Timex.now()]
+ )
unprocessed_empty_blocks_from_db = unprocessed_empty_blocks_query_list(limit())
@@ -97,7 +96,7 @@ defmodule Indexer.Fetcher.EmptyBlocksSanitizer do
if transactions_count > 0 do
Logger.info(
- "Block with number #{block_number} and hash #{to_string(block_hash)} is full of transactions. We should set consensus=false for it in order to refetch.",
+ "Block with number #{block_number} and hash #{to_string(block_hash)} is full of transactions. We should set consensus = false for it in order to refetch.",
fetcher: :empty_blocks_to_refetch
)
@@ -135,31 +134,29 @@ defmodule Indexer.Fetcher.EmptyBlocksSanitizer do
{:error, %{exception: postgrex_error}}
end
+ @head_offset 1000
defp consensus_blocks_with_nil_is_empty_query(limit) do
+ safe_block_number = BlockNumber.get_max() - @head_offset
+
from(block in Block,
where: is_nil(block.is_empty),
+ where: block.number <= ^safe_block_number,
where: block.consensus == true,
order_by: [asc: block.hash],
- limit: ^limit,
- offset: 1000,
- lock: "FOR UPDATE"
+ limit: ^limit
)
end
- defp unprocessed_non_empty_blocks_query_list(limit) do
+ defp unprocessed_non_empty_blocks_query(limit) do
blocks_query = consensus_blocks_with_nil_is_empty_query(limit)
- query =
- from(q in subquery(blocks_query),
- inner_join: transaction in Transaction,
- on: q.number == transaction.block_number,
- select: q.hash,
- distinct: q.hash,
- order_by: [asc: q.hash]
- )
-
- query
- |> Repo.all(timeout: :infinity)
+ from(q in subquery(blocks_query),
+ inner_join: transaction in Transaction,
+ on: q.number == transaction.block_number,
+ select: q.hash,
+ order_by: [asc: q.hash],
+ lock: fragment("FOR NO KEY UPDATE OF ?", q)
+ )
end
defp unprocessed_empty_blocks_query_list(limit) do
diff --git a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex
index 1f249da9f1e2..d5d1af69436a 100644
--- a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex
+++ b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex
@@ -12,6 +12,7 @@ defmodule Indexer.Fetcher.InternalTransaction do
import Indexer.Block.Fetcher, only: [async_import_coin_balances: 2]
+ alias EthereumJSONRPC.Utility.RangesHelper
alias Explorer.Chain
alias Explorer.Chain.Block
alias Explorer.Chain.Cache.{Accounts, Blocks}
@@ -71,9 +72,12 @@ defmodule Indexer.Fetcher.InternalTransaction do
@impl BufferedTask
def init(initial, reducer, _json_rpc_named_arguments) do
{:ok, final} =
- Chain.stream_blocks_with_unfetched_internal_transactions(initial, fn block_number, acc ->
- reducer.(block_number, acc)
- end)
+ Chain.stream_blocks_with_unfetched_internal_transactions(
+ initial,
+ fn block_number, acc ->
+ reducer.(block_number, acc)
+ end
+ )
final
end
@@ -96,8 +100,9 @@ defmodule Indexer.Fetcher.InternalTransaction do
|> Chain.filter_consensus_block_numbers()
filtered_unique_numbers =
- EthereumJSONRPC.block_numbers_in_range(unique_numbers) --
- [EthereumJSONRPC.first_block_to_fetch(:trace_first_block)]
+ unique_numbers
+ |> RangesHelper.filter_traceable_block_numbers()
+ |> drop_genesis(json_rpc_named_arguments)
filtered_unique_numbers_count = Enum.count(filtered_unique_numbers)
Logger.metadata(count: filtered_unique_numbers_count)
@@ -106,18 +111,7 @@ defmodule Indexer.Fetcher.InternalTransaction do
json_rpc_named_arguments
|> Keyword.fetch!(:variant)
- |> case do
- variant when variant in [EthereumJSONRPC.Nethermind, EthereumJSONRPC.Erigon, EthereumJSONRPC.Besu] ->
- EthereumJSONRPC.fetch_block_internal_transactions(filtered_unique_numbers, json_rpc_named_arguments)
-
- _ ->
- try do
- fetch_block_internal_transactions_by_transactions(filtered_unique_numbers, json_rpc_named_arguments)
- rescue
- error ->
- {:error, error, __STACKTRACE__}
- end
- end
+ |> fetch_internal_transactions(filtered_unique_numbers, json_rpc_named_arguments)
|> case do
{:ok, internal_transactions_params} ->
safe_import_internal_transaction(internal_transactions_params, filtered_unique_numbers)
@@ -153,6 +147,46 @@ defmodule Indexer.Fetcher.InternalTransaction do
end
end
+ defp fetch_internal_transactions(variant, block_numbers, json_rpc_named_arguments) do
+ if variant in block_traceable_variants() do
+ EthereumJSONRPC.fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments)
+ else
+ try do
+ fetch_block_internal_transactions_by_transactions(block_numbers, json_rpc_named_arguments)
+ rescue
+ error ->
+ {:error, error, __STACKTRACE__}
+ end
+ end
+ end
+
+ @default_block_traceable_variants [
+ EthereumJSONRPC.Nethermind,
+ EthereumJSONRPC.Erigon,
+ EthereumJSONRPC.Besu,
+ EthereumJSONRPC.RSK
+ ]
+ defp block_traceable_variants do
+ if Application.get_env(:ethereum_jsonrpc, EthereumJSONRPC.Geth)[:block_traceable?] do
+ [EthereumJSONRPC.Geth | @default_block_traceable_variants]
+ else
+ @default_block_traceable_variants
+ end
+ end
+
+ defp drop_genesis(block_numbers, json_rpc_named_arguments) do
+ first_block = Application.get_env(:indexer, :trace_first_block)
+
+ if first_block in block_numbers do
+ case EthereumJSONRPC.fetch_blocks_by_numbers([first_block], json_rpc_named_arguments) do
+ {:ok, %{transactions_params: [_ | _]}} -> block_numbers
+ _ -> block_numbers -- [first_block]
+ end
+ else
+ block_numbers
+ end
+ end
+
def import_first_trace(internal_transactions_params) do
imports =
Chain.import(%{
@@ -220,7 +254,7 @@ defmodule Indexer.Fetcher.InternalTransaction do
address_hash_to_block_number =
Enum.into(addresses_params, %{}, fn %{fetched_coin_balance_block_number: block_number, hash: hash} ->
- {hash, block_number}
+ {String.downcase(hash), block_number}
end)
empty_block_numbers =
@@ -260,6 +294,8 @@ defmodule Indexer.Fetcher.InternalTransaction do
error_count: Enum.count(unique_numbers)
)
+ handle_unique_key_violation(reason, unique_numbers)
+
# re-queue the de-duped entries
{:retry, unique_numbers}
end
@@ -286,13 +322,26 @@ defmodule Indexer.Fetcher.InternalTransaction do
|> Map.delete(:created_contract_code)
|> Map.delete(:gas_used)
|> Map.delete(:output)
- |> Map.put(:error, failed_parent[:error])
+ |> Map.put(:error, internal_transaction_param[:error] || failed_parent[:error])
else
internal_transaction_param
end
end)
end
+ defp handle_unique_key_violation(%{exception: %{postgres: %{code: :unique_violation}}}, block_numbers) do
+ BlocksRunner.invalidate_consensus_blocks(block_numbers)
+
+ Logger.error(fn ->
+ [
+ "unique_violation on internal transactions import, block numbers: ",
+ inspect(block_numbers)
+ ]
+ end)
+ end
+
+ defp handle_unique_key_violation(_reason, _block_numbers), do: :ok
+
defp handle_foreign_key_violation(internal_transactions_params, block_numbers) do
BlocksRunner.invalidate_consensus_blocks(block_numbers)
@@ -330,12 +379,12 @@ defmodule Indexer.Fetcher.InternalTransaction do
defp invalidate_block_from_error(_error_data), do: :ok
- defp defaults do
+ def defaults do
[
+ poll: false,
flush_interval: :timer.seconds(3),
max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency,
max_batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @default_max_batch_size,
- poll: true,
task_supervisor: Indexer.Fetcher.InternalTransaction.TaskSupervisor,
metadata: [fetcher: :internal_transaction]
]
diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge.ex
new file mode 100644
index 000000000000..9365bd58609f
--- /dev/null
+++ b/apps/indexer/lib/indexer/fetcher/polygon_edge.ex
@@ -0,0 +1,837 @@
+defmodule Indexer.Fetcher.PolygonEdge do
+ @moduledoc """
+ Contains common functions for PolygonEdge.* fetchers.
+ """
+
+ use GenServer
+ use Indexer.Fetcher
+
+ require Logger
+
+ import Ecto.Query
+
+ import EthereumJSONRPC,
+ only: [fetch_block_number_by_tag: 2, json_rpc: 2, integer_to_quantity: 1, quantity_to_integer: 1, request: 1]
+
+ import Explorer.Helper, only: [parse_integer: 1]
+
+ alias EthereumJSONRPC.Block.ByNumber
+ alias Explorer.Chain.Events.Publisher
+ alias Explorer.{Chain, Repo}
+ alias Indexer.{BoundQueue, Helper}
+ alias Indexer.Fetcher.PolygonEdge.{Deposit, DepositExecute, Withdrawal, WithdrawalExit}
+
+ @fetcher_name :polygon_edge
+ @block_check_interval_range_size 100
+
+ def child_spec(start_link_arguments) do
+ spec = %{
+ id: __MODULE__,
+ start: {__MODULE__, :start_link, start_link_arguments},
+ restart: :transient,
+ type: :worker
+ }
+
+ Supervisor.child_spec(spec, [])
+ end
+
+ def start_link(args, gen_server_options \\ []) do
+ GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__))
+ end
+
+ @impl GenServer
+ def init(_args) do
+ Logger.metadata(fetcher: @fetcher_name)
+
+ modules_using_reorg_monitor = [Deposit, WithdrawalExit]
+
+ reorg_monitor_not_needed =
+ modules_using_reorg_monitor
+ |> Enum.all?(fn module ->
+ is_nil(Application.get_all_env(:indexer)[module][:start_block_l1])
+ end)
+
+ if reorg_monitor_not_needed do
+ :ignore
+ else
+ polygon_edge_l1_rpc = Application.get_all_env(:indexer)[Indexer.Fetcher.PolygonEdge][:polygon_edge_l1_rpc]
+
+ json_rpc_named_arguments = json_rpc_named_arguments(polygon_edge_l1_rpc)
+
+ {:ok, block_check_interval, _} = get_block_check_interval(json_rpc_named_arguments)
+
+ Process.send(self(), :reorg_monitor, [])
+
+ {:ok,
+ %{block_check_interval: block_check_interval, json_rpc_named_arguments: json_rpc_named_arguments, prev_latest: 0}}
+ end
+ end
+
+ @spec init_l1(
+ Explorer.Chain.PolygonEdge.Deposit | Explorer.Chain.PolygonEdge.WithdrawalExit,
+ list(),
+ pid(),
+ binary(),
+ binary(),
+ binary(),
+ binary()
+ ) :: {:ok, map()} | :ignore
+ def init_l1(table, env, pid, contract_address, contract_name, table_name, entity_name)
+ when table in [Explorer.Chain.PolygonEdge.Deposit, Explorer.Chain.PolygonEdge.WithdrawalExit] do
+ with {:start_block_l1_undefined, false} <- {:start_block_l1_undefined, is_nil(env[:start_block_l1])},
+ {:reorg_monitor_started, true} <-
+ {:reorg_monitor_started, !is_nil(Process.whereis(Indexer.Fetcher.PolygonEdge))},
+ polygon_edge_l1_rpc = Application.get_all_env(:indexer)[Indexer.Fetcher.PolygonEdge][:polygon_edge_l1_rpc],
+ {:rpc_l1_undefined, false} <- {:rpc_l1_undefined, is_nil(polygon_edge_l1_rpc)},
+ {:contract_is_valid, true} <- {:contract_is_valid, Helper.is_address_correct?(contract_address)},
+ start_block_l1 = parse_integer(env[:start_block_l1]),
+ false <- is_nil(start_block_l1),
+ true <- start_block_l1 > 0,
+ {last_l1_block_number, last_l1_transaction_hash} <- get_last_l1_item(table),
+ {:start_block_l1_valid, true} <-
+ {:start_block_l1_valid, start_block_l1 <= last_l1_block_number || last_l1_block_number == 0},
+ json_rpc_named_arguments = json_rpc_named_arguments(polygon_edge_l1_rpc),
+ {:ok, last_l1_tx} <-
+ get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments, 100_000_000),
+ {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)},
+ {:ok, block_check_interval, last_safe_block} <-
+ get_block_check_interval(json_rpc_named_arguments) do
+ start_block = max(start_block_l1, last_l1_block_number)
+
+ Process.send(pid, :continue, [])
+
+ {:ok,
+ %{
+ contract_address: contract_address,
+ block_check_interval: block_check_interval,
+ start_block: start_block,
+ end_block: last_safe_block,
+ json_rpc_named_arguments: json_rpc_named_arguments
+ }}
+ else
+ {:start_block_l1_undefined, true} ->
+ # the process shouldn't start if the start block is not defined
+ :ignore
+
+ {:reorg_monitor_started, false} ->
+ Logger.error("Cannot start this process as reorg monitor in Indexer.Fetcher.PolygonEdge is not started.")
+ :ignore
+
+ {:rpc_l1_undefined, true} ->
+ Logger.error("L1 RPC URL is not defined.")
+ :ignore
+
+ {:contract_is_valid, false} ->
+ Logger.error("#{contract_name} contract address is invalid or not defined.")
+ :ignore
+
+ {:start_block_l1_valid, false} ->
+ Logger.error("Invalid L1 Start Block value. Please, check the value and #{table_name} table.")
+
+ :ignore
+
+ {:error, error_data} ->
+ Logger.error(
+ "Cannot get last L1 transaction from RPC by its hash, last safe block, or block timestamp by its number due to RPC error: #{inspect(error_data)}"
+ )
+
+ :ignore
+
+ {:l1_tx_not_found, true} ->
+ Logger.error(
+ "Cannot find last L1 transaction from RPC by its hash. Probably, there was a reorg on L1 chain. Please, check #{table_name} table."
+ )
+
+ :ignore
+
+ _ ->
+ Logger.error("#{entity_name} L1 Start Block is invalid or zero.")
+ :ignore
+ end
+ end
+
+ @spec init_l2(
+ Explorer.Chain.PolygonEdge.DepositExecute | Explorer.Chain.PolygonEdge.Withdrawal,
+ list(),
+ pid(),
+ binary(),
+ binary(),
+ binary(),
+ binary(),
+ list()
+ ) :: {:ok, map()} | :ignore
+ def init_l2(table, env, pid, contract_address, contract_name, table_name, entity_name, json_rpc_named_arguments)
+ when table in [Explorer.Chain.PolygonEdge.DepositExecute, Explorer.Chain.PolygonEdge.Withdrawal] do
+ with {:start_block_l2_undefined, false} <- {:start_block_l2_undefined, is_nil(env[:start_block_l2])},
+ {:contract_address_valid, true} <- {:contract_address_valid, Helper.is_address_correct?(contract_address)},
+ start_block_l2 = parse_integer(env[:start_block_l2]),
+ false <- is_nil(start_block_l2),
+ true <- start_block_l2 > 0,
+ {last_l2_block_number, last_l2_transaction_hash} <- get_last_l2_item(table),
+ {safe_block, safe_block_is_latest} = get_safe_block(json_rpc_named_arguments),
+ {:start_block_l2_valid, true} <-
+ {:start_block_l2_valid,
+ (start_block_l2 <= last_l2_block_number || last_l2_block_number == 0) && start_block_l2 <= safe_block},
+ {:ok, last_l2_tx} <- get_transaction_by_hash(last_l2_transaction_hash, json_rpc_named_arguments, 100_000_000),
+ {:l2_tx_not_found, false} <- {:l2_tx_not_found, !is_nil(last_l2_transaction_hash) && is_nil(last_l2_tx)} do
+ Process.send(pid, :continue, [])
+
+ {:ok,
+ %{
+ start_block: max(start_block_l2, last_l2_block_number),
+ start_block_l2: start_block_l2,
+ safe_block: safe_block,
+ safe_block_is_latest: safe_block_is_latest,
+ contract_address: contract_address,
+ json_rpc_named_arguments: json_rpc_named_arguments
+ }}
+ else
+ {:start_block_l2_undefined, true} ->
+ # the process shouldn't start if the start block is not defined
+ :ignore
+
+ {:contract_address_valid, false} ->
+ Logger.error("#{contract_name} contract address is invalid or not defined.")
+ :ignore
+
+ {:start_block_l2_valid, false} ->
+ Logger.error("Invalid L2 Start Block value. Please, check the value and #{table_name} table.")
+
+ :ignore
+
+ {:error, error_data} ->
+ Logger.error("Cannot get last L2 transaction from RPC by its hash due to RPC error: #{inspect(error_data)}")
+
+ :ignore
+
+ {:l2_tx_not_found, true} ->
+ Logger.error(
+ "Cannot find last L2 transaction from RPC by its hash. Probably, there was a reorg on L2 chain. Please, check #{table_name} table."
+ )
+
+ :ignore
+
+ _ ->
+ Logger.error("#{entity_name} L2 Start Block is invalid or zero.")
+ :ignore
+ end
+ end
+
+ @impl GenServer
+ def handle_info(
+ :reorg_monitor,
+ %{
+ block_check_interval: block_check_interval,
+ json_rpc_named_arguments: json_rpc_named_arguments,
+ prev_latest: prev_latest
+ } = state
+ ) do
+ {:ok, latest} = get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000)
+
+ if latest < prev_latest do
+ Logger.warning("Reorg detected: previous latest block ##{prev_latest}, current latest block ##{latest}.")
+
+ Publisher.broadcast([{:polygon_edge_reorg_block, latest}], :realtime)
+ end
+
+ Process.send_after(self(), :reorg_monitor, block_check_interval)
+
+ {:noreply, %{state | prev_latest: latest}}
+ end
+
+ @spec handle_continue(map(), binary(), Deposit | WithdrawalExit, atom()) :: {:noreply, map()}
+ def handle_continue(
+ %{
+ contract_address: contract_address,
+ block_check_interval: block_check_interval,
+ start_block: start_block,
+ end_block: end_block,
+ json_rpc_named_arguments: json_rpc_named_arguments
+ } = state,
+ event_signature,
+ calling_module,
+ fetcher_name
+ )
+ when calling_module in [Deposit, WithdrawalExit] do
+ time_before = Timex.now()
+
+ eth_get_logs_range_size =
+ Application.get_all_env(:indexer)[Indexer.Fetcher.PolygonEdge][:polygon_edge_eth_get_logs_range_size]
+
+ chunks_number = ceil((end_block - start_block + 1) / eth_get_logs_range_size)
+ chunk_range = Range.new(0, max(chunks_number - 1, 0), 1)
+
+ last_written_block =
+ chunk_range
+ |> Enum.reduce_while(start_block - 1, fn current_chunk, _ ->
+ chunk_start = start_block + eth_get_logs_range_size * current_chunk
+ chunk_end = min(chunk_start + eth_get_logs_range_size - 1, end_block)
+
+ if chunk_end >= chunk_start do
+ log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, "L1")
+
+ {:ok, result} =
+ get_logs(
+ chunk_start,
+ chunk_end,
+ contract_address,
+ event_signature,
+ json_rpc_named_arguments,
+ 100_000_000
+ )
+
+ {events, event_name} =
+ result
+ |> calling_module.prepare_events(json_rpc_named_arguments)
+ |> import_events(calling_module)
+
+ log_blocks_chunk_handling(
+ chunk_start,
+ chunk_end,
+ start_block,
+ end_block,
+ "#{Enum.count(events)} #{event_name} event(s)",
+ "L1"
+ )
+ end
+
+ reorg_block = reorg_block_pop(fetcher_name)
+
+ if !is_nil(reorg_block) && reorg_block > 0 do
+ reorg_handle(reorg_block, calling_module)
+ {:halt, if(reorg_block <= chunk_end, do: reorg_block - 1, else: chunk_end)}
+ else
+ {:cont, chunk_end}
+ end
+ end)
+
+ new_start_block = last_written_block + 1
+ {:ok, new_end_block} = get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000)
+
+ delay =
+ if new_end_block == last_written_block do
+ # there is no new block, so wait for some time to let the chain issue the new block
+ max(block_check_interval - Timex.diff(Timex.now(), time_before, :milliseconds), 0)
+ else
+ 0
+ end
+
+ Process.send_after(self(), :continue, delay)
+
+ {:noreply, %{state | start_block: new_start_block, end_block: new_end_block}}
+ end
+
+ @spec fill_block_range(integer(), integer(), DepositExecute | Withdrawal, binary(), list(), boolean()) :: integer()
+ def fill_block_range(
+ l2_block_start,
+ l2_block_end,
+ calling_module,
+ contract_address,
+ json_rpc_named_arguments,
+ scan_db
+ )
+ when calling_module in [
+ DepositExecute,
+ Withdrawal
+ ] do
+ eth_get_logs_range_size =
+ Application.get_all_env(:indexer)[Indexer.Fetcher.PolygonEdge][:polygon_edge_eth_get_logs_range_size]
+
+ chunks_number =
+ if scan_db do
+ 1
+ else
+ ceil((l2_block_end - l2_block_start + 1) / eth_get_logs_range_size)
+ end
+
+ chunk_range = Range.new(0, max(chunks_number - 1, 0), 1)
+
+ Enum.reduce(chunk_range, 0, fn current_chunk, count_acc ->
+ chunk_start = l2_block_start + eth_get_logs_range_size * current_chunk
+
+ chunk_end =
+ if scan_db do
+ l2_block_end
+ else
+ min(chunk_start + eth_get_logs_range_size - 1, l2_block_end)
+ end
+
+ log_blocks_chunk_handling(chunk_start, chunk_end, l2_block_start, l2_block_end, nil, "L2")
+
+ count =
+ calling_module.find_and_save_entities(
+ scan_db,
+ contract_address,
+ chunk_start,
+ chunk_end,
+ json_rpc_named_arguments
+ )
+
+ event_name =
+ if calling_module == Indexer.Fetcher.PolygonEdge.DepositExecute do
+ "StateSyncResult"
+ else
+ "L2StateSynced"
+ end
+
+ log_blocks_chunk_handling(
+ chunk_start,
+ chunk_end,
+ l2_block_start,
+ l2_block_end,
+ "#{count} #{event_name} event(s)",
+ "L2"
+ )
+
+ count_acc + count
+ end)
+ end
+
+ @spec fill_block_range(integer(), integer(), {module(), module()}, binary(), list()) :: integer()
+ def fill_block_range(start_block, end_block, {module, table}, contract_address, json_rpc_named_arguments) do
+ fill_block_range(start_block, end_block, module, contract_address, json_rpc_named_arguments, true)
+
+ fill_msg_id_gaps(
+ start_block,
+ table,
+ module,
+ contract_address,
+ json_rpc_named_arguments,
+ false
+ )
+
+ {last_l2_block_number, _} = get_last_l2_item(table)
+
+ fill_block_range(
+ max(start_block, last_l2_block_number),
+ end_block,
+ module,
+ contract_address,
+ json_rpc_named_arguments,
+ false
+ )
+ end
+
+ @spec fill_msg_id_gaps(integer(), module(), module(), binary(), list(), boolean()) :: no_return()
+ def fill_msg_id_gaps(
+ start_block_l2,
+ table,
+ calling_module,
+ contract_address,
+ json_rpc_named_arguments,
+ scan_db \\ true
+ ) do
+ id_min = Repo.aggregate(table, :min, :msg_id)
+ id_max = Repo.aggregate(table, :max, :msg_id)
+
+ with true <- !is_nil(id_min) and !is_nil(id_max),
+ starts = msg_id_gap_starts(id_max, table),
+ ends = msg_id_gap_ends(id_min, table),
+ min_block_l2 = l2_block_number_by_msg_id(id_min, table),
+ {new_starts, new_ends} =
+ if(start_block_l2 < min_block_l2,
+ do: {[start_block_l2 | starts], [min_block_l2 | ends]},
+ else: {starts, ends}
+ ),
+ true <- Enum.count(new_starts) == Enum.count(new_ends) do
+ ranges = Enum.zip(new_starts, new_ends)
+
+ invalid_range_exists = Enum.any?(ranges, fn {l2_block_start, l2_block_end} -> l2_block_start > l2_block_end end)
+
+ ranges_final =
+ with {:ranges_are_invalid, true} <- {:ranges_are_invalid, invalid_range_exists},
+ {max_block_l2, _} = get_last_l2_item(table),
+ {:start_block_l2_is_min, true} <- {:start_block_l2_is_min, start_block_l2 <= max_block_l2} do
+ [{start_block_l2, max_block_l2}]
+ else
+ {:ranges_are_invalid, false} -> ranges
+ {:start_block_l2_is_min, false} -> []
+ end
+
+ ranges_final
+ |> Enum.each(fn {l2_block_start, l2_block_end} ->
+ count =
+ fill_block_range(
+ l2_block_start,
+ l2_block_end,
+ calling_module,
+ contract_address,
+ json_rpc_named_arguments,
+ scan_db
+ )
+
+ if count > 0 do
+ log_fill_msg_id_gaps(scan_db, l2_block_start, l2_block_end, table, count)
+ end
+ end)
+
+ if scan_db do
+ fill_msg_id_gaps(start_block_l2, table, calling_module, contract_address, json_rpc_named_arguments, false)
+ end
+ end
+ end
+
+ defp log_fill_msg_id_gaps(scan_db, l2_block_start, l2_block_end, table, count) do
+ find_place = if scan_db, do: "in DB", else: "through RPC"
+ table_name = table.__schema__(:source)
+
+ Logger.info(
+ "Filled gaps between L2 blocks #{l2_block_start} and #{l2_block_end}. #{count} event(s) were found #{find_place} and written to #{table_name} table."
+ )
+ end
+
+ defp msg_id_gap_starts(id_max, table)
+ when table in [Explorer.Chain.PolygonEdge.DepositExecute, Explorer.Chain.PolygonEdge.Withdrawal] do
+ query =
+ if table == Explorer.Chain.PolygonEdge.DepositExecute do
+ from(item in table,
+ select: item.l2_block_number,
+ order_by: item.msg_id,
+ where:
+ fragment(
+ "NOT EXISTS (SELECT msg_id FROM polygon_edge_deposit_executes WHERE msg_id = (? + 1)) AND msg_id != ?",
+ item.msg_id,
+ ^id_max
+ )
+ )
+ else
+ from(item in table,
+ select: item.l2_block_number,
+ order_by: item.msg_id,
+ where:
+ fragment(
+ "NOT EXISTS (SELECT msg_id FROM polygon_edge_withdrawals WHERE msg_id = (? + 1)) AND msg_id != ?",
+ item.msg_id,
+ ^id_max
+ )
+ )
+ end
+
+ Repo.all(query)
+ end
+
+ defp msg_id_gap_ends(id_min, table)
+ when table in [Explorer.Chain.PolygonEdge.DepositExecute, Explorer.Chain.PolygonEdge.Withdrawal] do
+ query =
+ if table == Explorer.Chain.PolygonEdge.DepositExecute do
+ from(item in table,
+ select: item.l2_block_number,
+ order_by: item.msg_id,
+ where:
+ fragment(
+ "NOT EXISTS (SELECT msg_id FROM polygon_edge_deposit_executes WHERE msg_id = (? - 1)) AND msg_id != ?",
+ item.msg_id,
+ ^id_min
+ )
+ )
+ else
+ from(item in table,
+ select: item.l2_block_number,
+ order_by: item.msg_id,
+ where:
+ fragment(
+ "NOT EXISTS (SELECT msg_id FROM polygon_edge_withdrawals WHERE msg_id = (? - 1)) AND msg_id != ?",
+ item.msg_id,
+ ^id_min
+ )
+ )
+ end
+
+ Repo.all(query)
+ end
+
+ defp get_block_check_interval(json_rpc_named_arguments) do
+ {last_safe_block, _} = get_safe_block(json_rpc_named_arguments)
+
+ first_block = max(last_safe_block - @block_check_interval_range_size, 1)
+
+ with {:ok, first_block_timestamp} <-
+ get_block_timestamp_by_number(first_block, json_rpc_named_arguments, 100_000_000),
+ {:ok, last_safe_block_timestamp} <-
+ get_block_timestamp_by_number(last_safe_block, json_rpc_named_arguments, 100_000_000) do
+ block_check_interval =
+ ceil((last_safe_block_timestamp - first_block_timestamp) / (last_safe_block - first_block) * 1000 / 2)
+
+ Logger.info("Block check interval is calculated as #{block_check_interval} ms.")
+ {:ok, block_check_interval, last_safe_block}
+ else
+ {:error, error} ->
+ {:error, "Failed to calculate block check interval due to #{inspect(error)}"}
+ end
+ end
+
+ @spec get_block_number_by_tag(binary(), list(), integer()) :: {:ok, non_neg_integer()} | {:error, atom()}
+ def get_block_number_by_tag(tag, json_rpc_named_arguments, retries \\ 3) do
+ error_message = &"Cannot fetch #{tag} block number. Error: #{inspect(&1)}"
+ repeated_call(&fetch_block_number_by_tag/2, [tag, json_rpc_named_arguments], error_message, retries)
+ end
+
+ defp get_block_timestamp_by_number_inner(number, json_rpc_named_arguments) do
+ result =
+ %{id: 0, number: number}
+ |> ByNumber.request(false)
+ |> json_rpc(json_rpc_named_arguments)
+
+ with {:ok, block} <- result,
+ false <- is_nil(block),
+ timestamp <- Map.get(block, "timestamp"),
+ false <- is_nil(timestamp) do
+ {:ok, quantity_to_integer(timestamp)}
+ else
+ {:error, message} ->
+ {:error, message}
+
+ true ->
+ {:error, "RPC returned nil."}
+ end
+ end
+
+ defp get_block_timestamp_by_number(number, json_rpc_named_arguments, retries) do
+ func = &get_block_timestamp_by_number_inner/2
+ args = [number, json_rpc_named_arguments]
+ error_message = &"Cannot fetch block ##{number} or its timestamp. Error: #{inspect(&1)}"
+ repeated_call(func, args, error_message, retries)
+ end
+
+ defp get_safe_block(json_rpc_named_arguments) do
+ case get_block_number_by_tag("safe", json_rpc_named_arguments) do
+ {:ok, safe_block} ->
+ {safe_block, false}
+
+ {:error, :not_found} ->
+ {:ok, latest_block} = get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000)
+ {latest_block, true}
+ end
+ end
+
+ @spec get_logs(
+ non_neg_integer() | binary(),
+ non_neg_integer() | binary(),
+ binary(),
+ binary(),
+ list(),
+ non_neg_integer()
+ ) :: {:ok, list()} | {:error, term()}
+ def get_logs(from_block, to_block, address, topic0, json_rpc_named_arguments, retries) do
+ processed_from_block = if is_integer(from_block), do: integer_to_quantity(from_block), else: from_block
+ processed_to_block = if is_integer(to_block), do: integer_to_quantity(to_block), else: to_block
+
+ req =
+ request(%{
+ id: 0,
+ method: "eth_getLogs",
+ params: [
+ %{
+ :fromBlock => processed_from_block,
+ :toBlock => processed_to_block,
+ :address => address,
+ :topics => [topic0]
+ }
+ ]
+ })
+
+ error_message = &"Cannot fetch logs for the block range #{from_block}..#{to_block}. Error: #{inspect(&1)}"
+
+ repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries)
+ end
+
+ defp get_transaction_by_hash(hash, _json_rpc_named_arguments, _retries_left) when is_nil(hash), do: {:ok, nil}
+
+ defp get_transaction_by_hash(hash, json_rpc_named_arguments, retries) do
+ req =
+ request(%{
+ id: 0,
+ method: "eth_getTransactionByHash",
+ params: [hash]
+ })
+
+ error_message = &"eth_getTransactionByHash failed. Error: #{inspect(&1)}"
+
+ repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries)
+ end
+
+ defp get_last_l1_item(table) do
+ query =
+ from(item in table,
+ select: {item.l1_block_number, item.l1_transaction_hash},
+ order_by: [desc: item.msg_id],
+ limit: 1
+ )
+
+ query
+ |> Repo.one()
+ |> Kernel.||({0, nil})
+ end
+
+ @spec get_last_l2_item(module()) :: {non_neg_integer(), binary() | nil}
+ def get_last_l2_item(table) do
+ query =
+ from(item in table,
+ select: {item.l2_block_number, item.l2_transaction_hash},
+ order_by: [desc: item.msg_id],
+ limit: 1
+ )
+
+ query
+ |> Repo.one()
+ |> Kernel.||({0, nil})
+ end
+
+ defp json_rpc_named_arguments(polygon_edge_l1_rpc) do
+ [
+ transport: EthereumJSONRPC.HTTP,
+ transport_options: [
+ http: EthereumJSONRPC.HTTP.HTTPoison,
+ url: polygon_edge_l1_rpc,
+ http_options: [
+ recv_timeout: :timer.minutes(10),
+ timeout: :timer.minutes(10),
+ hackney: [pool: :ethereum_jsonrpc]
+ ]
+ ]
+ ]
+ end
+
+ defp l2_block_number_by_msg_id(id, table) do
+ Repo.one(from(item in table, select: item.l2_block_number, where: item.msg_id == ^id))
+ end
+
+ defp log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, items_count, layer) do
+ is_start = is_nil(items_count)
+
+ {type, found} =
+ if is_start do
+ {"Start", ""}
+ else
+ {"Finish", " Found #{items_count}."}
+ end
+
+ target_range =
+ if chunk_start != start_block or chunk_end != end_block do
+ progress =
+ if is_start do
+ ""
+ else
+ percentage =
+ (chunk_end - start_block + 1)
+ |> Decimal.div(end_block - start_block + 1)
+ |> Decimal.mult(100)
+ |> Decimal.round(2)
+ |> Decimal.to_string()
+
+ " Progress: #{percentage}%"
+ end
+
+ " Target range: #{start_block}..#{end_block}.#{progress}"
+ else
+ ""
+ end
+
+ if chunk_start == chunk_end do
+ Logger.info("#{type} handling #{layer} block ##{chunk_start}.#{found}#{target_range}")
+ else
+ Logger.info("#{type} handling #{layer} block range #{chunk_start}..#{chunk_end}.#{found}#{target_range}")
+ end
+ end
+
+ defp import_events(events, calling_module) do
+ {import_data, event_name} =
+ if calling_module == Deposit do
+ {%{polygon_edge_deposits: %{params: events}, timeout: :infinity}, "StateSynced"}
+ else
+ {%{polygon_edge_withdrawal_exits: %{params: events}, timeout: :infinity}, "ExitProcessed"}
+ end
+
+ {:ok, _} = Chain.import(import_data)
+
+ {events, event_name}
+ end
+
+ defp log_deleted_rows_count(reorg_block, count, table_name) do
+ if count > 0 do
+ Logger.warning(
+ "As L1 reorg was detected, all rows with l1_block_number >= #{reorg_block} were removed from the #{table_name} table. Number of removed rows: #{count}."
+ )
+ end
+ end
+
+ defp repeated_call(func, args, error_message, retries_left) do
+ case apply(func, args) do
+ {:ok, _} = res ->
+ res
+
+ {:error, message} = err ->
+ retries_left = retries_left - 1
+
+ if retries_left <= 0 do
+ Logger.error(error_message.(message))
+ err
+ else
+ Logger.error("#{error_message.(message)} Retrying...")
+ :timer.sleep(3000)
+ repeated_call(func, args, error_message, retries_left)
+ end
+ end
+ end
+
+ @spec repeated_request(list(), any(), list(), non_neg_integer()) :: {:ok, any()} | {:error, atom()}
+ def repeated_request(req, error_message, json_rpc_named_arguments, retries) do
+ repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries)
+ end
+
+ defp reorg_block_pop(fetcher_name) do
+ table_name = reorg_table_name(fetcher_name)
+
+ case BoundQueue.pop_front(reorg_queue_get(table_name)) do
+ {:ok, {block_number, updated_queue}} ->
+ :ets.insert(table_name, {:queue, updated_queue})
+ block_number
+
+ {:error, :empty} ->
+ nil
+ end
+ end
+
+ @spec reorg_block_push(atom(), non_neg_integer()) :: no_return()
+ def reorg_block_push(fetcher_name, block_number) do
+ table_name = reorg_table_name(fetcher_name)
+ {:ok, updated_queue} = BoundQueue.push_back(reorg_queue_get(table_name), block_number)
+ :ets.insert(table_name, {:queue, updated_queue})
+ end
+
+ defp reorg_handle(reorg_block, calling_module) do
+ {table, table_name} =
+ if calling_module == Deposit do
+ {Explorer.Chain.PolygonEdge.Deposit, "polygon_edge_deposits"}
+ else
+ {Explorer.Chain.PolygonEdge.WithdrawalExit, "polygon_edge_withdrawal_exits"}
+ end
+
+ {deleted_count, _} = Repo.delete_all(from(item in table, where: item.l1_block_number >= ^reorg_block))
+
+ log_deleted_rows_count(reorg_block, deleted_count, table_name)
+ end
+
+ defp reorg_queue_get(table_name) do
+ if :ets.whereis(table_name) == :undefined do
+ :ets.new(table_name, [
+ :set,
+ :named_table,
+ :public,
+ read_concurrency: true,
+ write_concurrency: true
+ ])
+ end
+
+ with info when info != :undefined <- :ets.info(table_name),
+ [{_, value}] <- :ets.lookup(table_name, :queue) do
+ value
+ else
+ _ -> %BoundQueue{}
+ end
+ end
+
+ defp reorg_table_name(fetcher_name) do
+ :"#{fetcher_name}#{:_reorgs}"
+ end
+end
diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit.ex
new file mode 100644
index 000000000000..ca11c30e08c8
--- /dev/null
+++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit.ex
@@ -0,0 +1,140 @@
+defmodule Indexer.Fetcher.PolygonEdge.Deposit do
+ @moduledoc """
+ Fills polygon_edge_deposits DB table.
+ """
+
+ use GenServer
+ use Indexer.Fetcher
+
+ require Logger
+
+ import EthereumJSONRPC, only: [quantity_to_integer: 1]
+ import Explorer.Helper, only: [decode_data: 2]
+
+ alias ABI.TypeDecoder
+ alias EthereumJSONRPC.Block.ByNumber
+ alias EthereumJSONRPC.Blocks
+ alias Explorer.Chain.Events.Subscriber
+ alias Explorer.Chain.PolygonEdge.Deposit
+ alias Indexer.Fetcher.PolygonEdge
+
+ @fetcher_name :polygon_edge_deposit
+
+ # 32-byte signature of the event StateSynced(uint256 indexed id, address indexed sender, address indexed receiver, bytes data)
+ @state_synced_event "0xd1d7f6609674cc5871fdb4b0bcd4f0a214118411de9e38983866514f22659165"
+
+ # 32-byte representation of deposit signature, keccak256("DEPOSIT")
+ @deposit_signature "87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f821"
+
+ def child_spec(start_link_arguments) do
+ spec = %{
+ id: __MODULE__,
+ start: {__MODULE__, :start_link, start_link_arguments},
+ restart: :transient,
+ type: :worker
+ }
+
+ Supervisor.child_spec(spec, [])
+ end
+
+ def start_link(args, gen_server_options \\ []) do
+ GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__))
+ end
+
+ @impl GenServer
+ def init(_args) do
+ Logger.metadata(fetcher: @fetcher_name)
+
+ env = Application.get_all_env(:indexer)[__MODULE__]
+
+ Subscriber.to(:polygon_edge_reorg_block, :realtime)
+
+ PolygonEdge.init_l1(
+ Deposit,
+ env,
+ self(),
+ env[:state_sender],
+ "State Sender",
+ "polygon_edge_deposits",
+ "Deposits"
+ )
+ end
+
+ @impl GenServer
+ def handle_info(:continue, state) do
+ PolygonEdge.handle_continue(state, @state_synced_event, __MODULE__, @fetcher_name)
+ end
+
+ @impl GenServer
+ def handle_info({:chain_event, :polygon_edge_reorg_block, :realtime, block_number}, state) do
+ PolygonEdge.reorg_block_push(@fetcher_name, block_number)
+ {:noreply, state}
+ end
+
+ @impl GenServer
+ def handle_info({ref, _result}, state) do
+ Process.demonitor(ref, [:flush])
+ {:noreply, state}
+ end
+
+ @spec prepare_events(list(), list()) :: list()
+ def prepare_events(events, json_rpc_named_arguments) do
+ Enum.map(events, fn event ->
+ [data_bytes] = decode_data(event["data"], [:bytes])
+
+ sig = binary_part(data_bytes, 0, 32)
+
+ l1_block_number = quantity_to_integer(event["blockNumber"])
+
+ {from, to, l1_timestamp} =
+ if Base.encode16(sig, case: :lower) == @deposit_signature do
+ timestamps = get_timestamps_by_events(events, json_rpc_named_arguments)
+
+ [_sig, _root_token, sender, receiver, _amount] =
+ TypeDecoder.decode_raw(data_bytes, [{:bytes, 32}, :address, :address, :address, {:uint, 256}])
+
+ {sender, receiver, Map.get(timestamps, l1_block_number)}
+ else
+ {nil, nil, nil}
+ end
+
+ %{
+ msg_id: quantity_to_integer(Enum.at(event["topics"], 1)),
+ from: from,
+ to: to,
+ l1_transaction_hash: event["transactionHash"],
+ l1_timestamp: l1_timestamp,
+ l1_block_number: l1_block_number
+ }
+ end)
+ end
+
+ defp get_blocks_by_events(events, json_rpc_named_arguments, retries) do
+ request =
+ events
+ |> Enum.reduce(%{}, fn event, acc ->
+ Map.put(acc, event["blockNumber"], 0)
+ end)
+ |> Stream.map(fn {block_number, _} -> %{number: block_number} end)
+ |> Stream.with_index()
+ |> Enum.into(%{}, fn {params, id} -> {id, params} end)
+ |> Blocks.requests(&ByNumber.request(&1, false, false))
+
+ error_message = &"Cannot fetch blocks with batch request. Error: #{inspect(&1)}. Request: #{inspect(request)}"
+
+ case PolygonEdge.repeated_request(request, error_message, json_rpc_named_arguments, retries) do
+ {:ok, results} -> Enum.map(results, fn %{result: result} -> result end)
+ {:error, _} -> []
+ end
+ end
+
+ defp get_timestamps_by_events(events, json_rpc_named_arguments) do
+ events
+ |> get_blocks_by_events(json_rpc_named_arguments, 100_000_000)
+ |> Enum.reduce(%{}, fn block, acc ->
+ block_number = quantity_to_integer(Map.get(block, "number"))
+ {:ok, timestamp} = DateTime.from_unix(quantity_to_integer(Map.get(block, "timestamp")))
+ Map.put(acc, block_number, timestamp)
+ end)
+ end
+end
diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex
new file mode 100644
index 000000000000..0807c1bc8dfd
--- /dev/null
+++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex
@@ -0,0 +1,207 @@
+defmodule Indexer.Fetcher.PolygonEdge.DepositExecute do
+ @moduledoc """
+ Fills polygon_edge_deposit_executes DB table.
+ """
+
+ use GenServer
+ use Indexer.Fetcher
+
+ require Logger
+
+ import Ecto.Query
+
+ import EthereumJSONRPC, only: [quantity_to_integer: 1]
+ import Indexer.Fetcher.PolygonEdge, only: [fill_block_range: 5, get_block_number_by_tag: 3]
+ import Indexer.Helper, only: [log_topic_to_string: 1]
+
+ alias Explorer.{Chain, Repo}
+ alias Explorer.Chain.Log
+ alias Explorer.Chain.PolygonEdge.DepositExecute
+ alias Indexer.Fetcher.PolygonEdge
+
+ @fetcher_name :polygon_edge_deposit_execute
+
+ # 32-byte signature of the event StateSyncResult(uint256 indexed counter, bool indexed status, bytes message)
+ @state_sync_result_event "0x31c652130602f3ce96ceaf8a4c2b8b49f049166c6fcf2eb31943a75ec7c936ae"
+
+ def child_spec(start_link_arguments) do
+ spec = %{
+ id: __MODULE__,
+ start: {__MODULE__, :start_link, start_link_arguments},
+ restart: :transient,
+ type: :worker
+ }
+
+ Supervisor.child_spec(spec, [])
+ end
+
+ def start_link(args, gen_server_options \\ []) do
+ GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__))
+ end
+
+ @impl GenServer
+ def init(args) do
+ Logger.metadata(fetcher: @fetcher_name)
+
+ json_rpc_named_arguments = args[:json_rpc_named_arguments]
+ env = Application.get_all_env(:indexer)[__MODULE__]
+
+ PolygonEdge.init_l2(
+ DepositExecute,
+ env,
+ self(),
+ env[:state_receiver],
+ "StateReceiver",
+ "polygon_edge_deposit_executes",
+ "Deposit Executes",
+ json_rpc_named_arguments
+ )
+ end
+
+ @impl GenServer
+ def handle_info(
+ :continue,
+ %{
+ start_block_l2: start_block_l2,
+ contract_address: contract_address,
+ json_rpc_named_arguments: json_rpc_named_arguments
+ } = state
+ ) do
+ PolygonEdge.fill_msg_id_gaps(
+ start_block_l2,
+ DepositExecute,
+ __MODULE__,
+ contract_address,
+ json_rpc_named_arguments
+ )
+
+ Process.send(self(), :find_new_events, [])
+ {:noreply, state}
+ end
+
+ @impl GenServer
+ def handle_info(
+ :find_new_events,
+ %{
+ start_block: start_block,
+ safe_block: safe_block,
+ safe_block_is_latest: safe_block_is_latest,
+ contract_address: contract_address,
+ json_rpc_named_arguments: json_rpc_named_arguments
+ } = state
+ ) do
+ # find and fill all events between start_block and "safe" block
+ # the "safe" block can be "latest" (when safe_block_is_latest == true)
+ fill_block_range(
+ start_block,
+ safe_block,
+ {__MODULE__, DepositExecute},
+ contract_address,
+ json_rpc_named_arguments
+ )
+
+ if not safe_block_is_latest do
+ # find and fill all events between "safe" and "latest" block (excluding "safe")
+ {:ok, latest_block} = get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000)
+
+ fill_block_range(
+ safe_block + 1,
+ latest_block,
+ {__MODULE__, DepositExecute},
+ contract_address,
+ json_rpc_named_arguments
+ )
+ end
+
+ {:stop, :normal, state}
+ end
+
+ @impl GenServer
+ def handle_info({ref, _result}, state) do
+ Process.demonitor(ref, [:flush])
+ {:noreply, state}
+ end
+
+ @spec remove(non_neg_integer()) :: no_return()
+ def remove(starting_block) do
+ Repo.delete_all(from(de in DepositExecute, where: de.l2_block_number >= ^starting_block))
+ end
+
+ @spec event_to_deposit_execute(binary(), binary(), binary(), binary()) :: map()
+ def event_to_deposit_execute(second_topic, third_topic, l2_transaction_hash, l2_block_number) do
+ msg_id =
+ second_topic
+ |> log_topic_to_string()
+ |> quantity_to_integer()
+
+ status =
+ third_topic
+ |> log_topic_to_string()
+ |> quantity_to_integer()
+
+ %{
+ msg_id: msg_id,
+ l2_transaction_hash: l2_transaction_hash,
+ l2_block_number: quantity_to_integer(l2_block_number),
+ success: status != 0
+ }
+ end
+
+ @spec find_and_save_entities(boolean(), binary(), non_neg_integer(), non_neg_integer(), list()) :: non_neg_integer()
+ def find_and_save_entities(
+ scan_db,
+ state_receiver,
+ block_start,
+ block_end,
+ json_rpc_named_arguments
+ ) do
+ executes =
+ if scan_db do
+ query =
+ from(log in Log,
+ select: {log.second_topic, log.third_topic, log.transaction_hash, log.block_number},
+ where:
+ log.first_topic == ^@state_sync_result_event and log.address_hash == ^state_receiver and
+ log.block_number >= ^block_start and log.block_number <= ^block_end
+ )
+
+ query
+ |> Repo.all(timeout: :infinity)
+ |> Enum.map(fn {second_topic, third_topic, l2_transaction_hash, l2_block_number} ->
+ event_to_deposit_execute(second_topic, third_topic, l2_transaction_hash, l2_block_number)
+ end)
+ else
+ {:ok, result} =
+ PolygonEdge.get_logs(
+ block_start,
+ block_end,
+ state_receiver,
+ @state_sync_result_event,
+ json_rpc_named_arguments,
+ 100_000_000
+ )
+
+ Enum.map(result, fn event ->
+ event_to_deposit_execute(
+ Enum.at(event["topics"], 1),
+ Enum.at(event["topics"], 2),
+ event["transactionHash"],
+ event["blockNumber"]
+ )
+ end)
+ end
+
+ {:ok, _} =
+ Chain.import(%{
+ polygon_edge_deposit_executes: %{params: executes},
+ timeout: :infinity
+ })
+
+ Enum.count(executes)
+ end
+
+ @spec state_sync_result_event_signature() :: binary()
+ def state_sync_result_event_signature do
+ @state_sync_result_event
+ end
+end
diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex
new file mode 100644
index 000000000000..8520cce2c155
--- /dev/null
+++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex
@@ -0,0 +1,222 @@
+defmodule Indexer.Fetcher.PolygonEdge.Withdrawal do
+ @moduledoc """
+ Fills polygon_edge_withdrawals DB table.
+ """
+
+ use GenServer
+ use Indexer.Fetcher
+
+ require Logger
+
+ import Ecto.Query
+
+ import EthereumJSONRPC, only: [quantity_to_integer: 1]
+ import Explorer.Helper, only: [decode_data: 2]
+ import Indexer.Fetcher.PolygonEdge, only: [fill_block_range: 5, get_block_number_by_tag: 3]
+ import Indexer.Helper, only: [log_topic_to_string: 1]
+
+ alias ABI.TypeDecoder
+ alias Explorer.{Chain, Repo}
+ alias Explorer.Chain.Log
+ alias Explorer.Chain.PolygonEdge.Withdrawal
+ alias Indexer.Fetcher.PolygonEdge
+
+ @fetcher_name :polygon_edge_withdrawal
+
+ # 32-byte signature of the event L2StateSynced(uint256 indexed id, address indexed sender, address indexed receiver, bytes data)
+ @l2_state_synced_event "0xedaf3c471ebd67d60c29efe34b639ede7d6a1d92eaeb3f503e784971e67118a5"
+
+ # 32-byte representation of withdrawal signature, keccak256("WITHDRAW")
+ @withdrawal_signature "7a8dc26796a1e50e6e190b70259f58f6a4edd5b22280ceecc82b687b8e982869"
+
+ def child_spec(start_link_arguments) do
+ spec = %{
+ id: __MODULE__,
+ start: {__MODULE__, :start_link, start_link_arguments},
+ restart: :transient,
+ type: :worker
+ }
+
+ Supervisor.child_spec(spec, [])
+ end
+
+ def start_link(args, gen_server_options \\ []) do
+ GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__))
+ end
+
+ @impl GenServer
+ def init(args) do
+ Logger.metadata(fetcher: @fetcher_name)
+
+ json_rpc_named_arguments = args[:json_rpc_named_arguments]
+ env = Application.get_all_env(:indexer)[__MODULE__]
+
+ PolygonEdge.init_l2(
+ Withdrawal,
+ env,
+ self(),
+ env[:state_sender],
+ "L2StateSender",
+ "polygon_edge_withdrawals",
+ "Withdrawals",
+ json_rpc_named_arguments
+ )
+ end
+
+ @impl GenServer
+ def handle_info(
+ :continue,
+ %{
+ start_block_l2: start_block_l2,
+ contract_address: contract_address,
+ json_rpc_named_arguments: json_rpc_named_arguments
+ } = state
+ ) do
+ PolygonEdge.fill_msg_id_gaps(
+ start_block_l2,
+ Withdrawal,
+ __MODULE__,
+ contract_address,
+ json_rpc_named_arguments
+ )
+
+ Process.send(self(), :find_new_events, [])
+ {:noreply, state}
+ end
+
+ @impl GenServer
+ def handle_info(
+ :find_new_events,
+ %{
+ start_block: start_block,
+ safe_block: safe_block,
+ safe_block_is_latest: safe_block_is_latest,
+ contract_address: contract_address,
+ json_rpc_named_arguments: json_rpc_named_arguments
+ } = state
+ ) do
+ # find and fill all events between start_block and "safe" block
+ # the "safe" block can be "latest" (when safe_block_is_latest == true)
+ fill_block_range(
+ start_block,
+ safe_block,
+ {__MODULE__, Withdrawal},
+ contract_address,
+ json_rpc_named_arguments
+ )
+
+ if not safe_block_is_latest do
+ # find and fill all events between "safe" and "latest" block (excluding "safe")
+ {:ok, latest_block} = get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000)
+
+ fill_block_range(
+ safe_block + 1,
+ latest_block,
+ {__MODULE__, Withdrawal},
+ contract_address,
+ json_rpc_named_arguments
+ )
+ end
+
+ {:stop, :normal, state}
+ end
+
+ @impl GenServer
+ def handle_info({ref, _result}, state) do
+ Process.demonitor(ref, [:flush])
+ {:noreply, state}
+ end
+
+ @spec remove(non_neg_integer()) :: no_return()
+ def remove(starting_block) do
+ Repo.delete_all(from(w in Withdrawal, where: w.l2_block_number >= ^starting_block))
+ end
+
+ @spec event_to_withdrawal(binary(), map(), binary(), binary()) :: map()
+ def event_to_withdrawal(second_topic, data, l2_transaction_hash, l2_block_number) do
+ msg_id =
+ second_topic
+ |> log_topic_to_string()
+ |> quantity_to_integer()
+
+ [data_bytes] = decode_data(data, [:bytes])
+
+ sig = binary_part(data_bytes, 0, 32)
+
+ {from, to} =
+ if Base.encode16(sig, case: :lower) == @withdrawal_signature do
+ [_sig, _root_token, sender, receiver, _amount] =
+ TypeDecoder.decode_raw(data_bytes, [{:bytes, 32}, :address, :address, :address, {:uint, 256}])
+
+ {sender, receiver}
+ else
+ {nil, nil}
+ end
+
+ %{
+ msg_id: msg_id,
+ from: from,
+ to: to,
+ l2_transaction_hash: l2_transaction_hash,
+ l2_block_number: quantity_to_integer(l2_block_number)
+ }
+ end
+
+ @spec find_and_save_entities(boolean(), binary(), non_neg_integer(), non_neg_integer(), list()) :: non_neg_integer()
+ def find_and_save_entities(
+ scan_db,
+ state_sender,
+ block_start,
+ block_end,
+ json_rpc_named_arguments
+ ) do
+ withdrawals =
+ if scan_db do
+ query =
+ from(log in Log,
+ select: {log.second_topic, log.data, log.transaction_hash, log.block_number},
+ where:
+ log.first_topic == ^@l2_state_synced_event and log.address_hash == ^state_sender and
+ log.block_number >= ^block_start and log.block_number <= ^block_end
+ )
+
+ query
+ |> Repo.all(timeout: :infinity)
+ |> Enum.map(fn {second_topic, data, l2_transaction_hash, l2_block_number} ->
+ event_to_withdrawal(second_topic, data, l2_transaction_hash, l2_block_number)
+ end)
+ else
+ {:ok, result} =
+ PolygonEdge.get_logs(
+ block_start,
+ block_end,
+ state_sender,
+ @l2_state_synced_event,
+ json_rpc_named_arguments,
+ 100_000_000
+ )
+
+ Enum.map(result, fn event ->
+ event_to_withdrawal(
+ Enum.at(event["topics"], 1),
+ event["data"],
+ event["transactionHash"],
+ event["blockNumber"]
+ )
+ end)
+ end
+
+ {:ok, _} =
+ Chain.import(%{
+ polygon_edge_withdrawals: %{params: withdrawals},
+ timeout: :infinity
+ })
+
+ Enum.count(withdrawals)
+ end
+
+ @spec l2_state_synced_event_signature() :: binary()
+ def l2_state_synced_event_signature do
+ @l2_state_synced_event
+ end
+end
diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal_exit.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal_exit.ex
new file mode 100644
index 000000000000..5b41e122ddc8
--- /dev/null
+++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal_exit.ex
@@ -0,0 +1,84 @@
+defmodule Indexer.Fetcher.PolygonEdge.WithdrawalExit do
+ @moduledoc """
+ Fills polygon_edge_withdrawal_exits DB table.
+ """
+
+ use GenServer
+ use Indexer.Fetcher
+
+ require Logger
+
+ import EthereumJSONRPC, only: [quantity_to_integer: 1]
+
+ alias Explorer.Chain.Events.Subscriber
+ alias Explorer.Chain.PolygonEdge.WithdrawalExit
+ alias Indexer.Fetcher.PolygonEdge
+
+ @fetcher_name :polygon_edge_withdrawal_exit
+
+ # 32-byte signature of the event ExitProcessed(uint256 indexed id, bool indexed success, bytes returnData)
+ @exit_processed_event "0x8bbfa0c9bee3785c03700d2a909592286efb83fc7e7002be5764424b9842f7ec"
+
+ def child_spec(start_link_arguments) do
+ spec = %{
+ id: __MODULE__,
+ start: {__MODULE__, :start_link, start_link_arguments},
+ restart: :transient,
+ type: :worker
+ }
+
+ Supervisor.child_spec(spec, [])
+ end
+
+ def start_link(args, gen_server_options \\ []) do
+ GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__))
+ end
+
+ @impl GenServer
+ def init(_args) do
+ Logger.metadata(fetcher: @fetcher_name)
+
+ env = Application.get_all_env(:indexer)[__MODULE__]
+
+ Subscriber.to(:polygon_edge_reorg_block, :realtime)
+
+ PolygonEdge.init_l1(
+ WithdrawalExit,
+ env,
+ self(),
+ env[:exit_helper],
+ "Exit Helper",
+ "polygon_edge_withdrawal_exits",
+ "Withdrawals"
+ )
+ end
+
+ @impl GenServer
+ def handle_info(:continue, state) do
+ PolygonEdge.handle_continue(state, @exit_processed_event, __MODULE__, @fetcher_name)
+ end
+
+ @impl GenServer
+ def handle_info({:chain_event, :polygon_edge_reorg_block, :realtime, block_number}, state) do
+ PolygonEdge.reorg_block_push(@fetcher_name, block_number)
+ {:noreply, state}
+ end
+
+ @impl GenServer
+ def handle_info({ref, _result}, state) do
+ Process.demonitor(ref, [:flush])
+ {:noreply, state}
+ end
+
+ @spec prepare_events(list(), list()) :: list()
+ def prepare_events(events, _) do
+ Enum.map(events, fn event ->
+ %{
+ msg_id: quantity_to_integer(Enum.at(event["topics"], 1)),
+ l1_transaction_hash: event["transactionHash"],
+ l1_block_number: quantity_to_integer(event["blockNumber"]),
+ success: quantity_to_integer(Enum.at(event["topics"], 2)) != 0
+ }
+ end)
+ end
+end
diff --git a/apps/indexer/lib/indexer/fetcher/replaced_transaction.ex b/apps/indexer/lib/indexer/fetcher/replaced_transaction.ex
index af64d8ba1258..88eba340c7c5 100644
--- a/apps/indexer/lib/indexer/fetcher/replaced_transaction.ex
+++ b/apps/indexer/lib/indexer/fetcher/replaced_transaction.ex
@@ -61,7 +61,8 @@ defmodule Indexer.Fetcher.ReplacedTransaction do
transaction_fields
|> pending_entry()
|> reducer.(acc)
- end
+ end,
+ true
)
final
diff --git a/apps/indexer/lib/indexer/fetcher/rootstock_data.ex b/apps/indexer/lib/indexer/fetcher/rootstock_data.ex
new file mode 100644
index 000000000000..09b683e6563a
--- /dev/null
+++ b/apps/indexer/lib/indexer/fetcher/rootstock_data.ex
@@ -0,0 +1,170 @@
+defmodule Indexer.Fetcher.RootstockData do
+ @moduledoc """
+ Refetch `minimum_gas_price`, `bitcoin_merged_mining_header`, `bitcoin_merged_mining_coinbase_transaction`,
+ `bitcoin_merged_mining_merkle_proof`, `hash_for_merged_mining` fields for blocks that were indexed before app update.
+ """
+
+ use GenServer
+ use Indexer.Fetcher
+
+ require Logger
+
+ alias EthereumJSONRPC.Blocks
+ alias Explorer.Chain.Block
+ alias Explorer.Repo
+
+ @interval :timer.seconds(3)
+ @batch_size 10
+ @concurrency 5
+ @db_batch_size 300
+
+ defstruct blocks_to_fetch: [],
+ interval: @interval,
+ json_rpc_named_arguments: [],
+ batch_size: @batch_size,
+ max_concurrency: @concurrency,
+ db_batch_size: @db_batch_size
+
+ def child_spec([init_arguments]) do
+ child_spec([init_arguments, []])
+ end
+
+ def child_spec([_init_arguments, _gen_server_options] = start_link_arguments) do
+ default = %{
+ id: __MODULE__,
+ start: {__MODULE__, :start_link, start_link_arguments}
+ }
+
+ Supervisor.child_spec(default, restart: :transient)
+ end
+
+ def start_link(arguments, gen_server_options \\ []) do
+ GenServer.start_link(__MODULE__, arguments, gen_server_options)
+ end
+
+ @impl GenServer
+ def init(opts) when is_list(opts) do
+ Logger.metadata(fetcher: :rootstock_data)
+
+ json_rpc_named_arguments = opts[:json_rpc_named_arguments]
+
+ unless json_rpc_named_arguments do
+ raise ArgumentError,
+ ":json_rpc_named_arguments must be provided to `#{__MODULE__}.init to allow for json_rpc calls when running."
+ end
+
+ state = %__MODULE__{
+ blocks_to_fetch: nil,
+ interval: opts[:interval] || Application.get_env(:indexer, __MODULE__)[:interval],
+ json_rpc_named_arguments: json_rpc_named_arguments,
+ batch_size: opts[:batch_size] || Application.get_env(:indexer, __MODULE__)[:batch_size],
+ max_concurrency: opts[:max_concurrency] || Application.get_env(:indexer, __MODULE__)[:max_concurrency],
+ db_batch_size: opts[:db_batch_size] || Application.get_env(:indexer, __MODULE__)[:db_batch_size]
+ }
+
+ Process.send_after(self(), :fetch_rootstock_data, state.interval)
+
+ {:ok, state, {:continue, :fetch_blocks}}
+ end
+
+ @impl GenServer
+ def handle_continue(:fetch_blocks, state), do: fetch_blocks(state)
+
+ @impl GenServer
+ def handle_info(:fetch_blocks, state), do: fetch_blocks(state)
+
+ @impl GenServer
+ def handle_info(
+ :fetch_rootstock_data,
+ %__MODULE__{
+ blocks_to_fetch: blocks_to_fetch,
+ interval: interval,
+ json_rpc_named_arguments: json_rpc_named_arguments,
+ batch_size: batch_size,
+ max_concurrency: concurrency
+ } = state
+ ) do
+ if Enum.empty?(blocks_to_fetch) do
+ send(self(), :fetch_blocks)
+ {:noreply, state}
+ else
+ new_blocks_to_fetch =
+ blocks_to_fetch
+ |> Stream.chunk_every(batch_size)
+ |> Task.async_stream(
+ &{EthereumJSONRPC.fetch_blocks_by_numbers(
+ Enum.map(&1, fn b -> b.number end),
+ json_rpc_named_arguments,
+ false
+ ), &1},
+ max_concurrency: concurrency,
+ timeout: :infinity,
+ zip_input_on_exit: true
+ )
+ |> Enum.reduce([], &fetch_reducer/2)
+
+ Process.send_after(self(), :fetch_rootstock_data, interval)
+
+ {:noreply, %__MODULE__{state | blocks_to_fetch: new_blocks_to_fetch}}
+ end
+ end
+
+ def handle_info({ref, _result}, state) do
+ Process.demonitor(ref, [:flush])
+ {:noreply, state}
+ end
+
+ def handle_info(
+ {:DOWN, _ref, :process, _pid, reason},
+ state
+ ) do
+ if reason === :normal do
+ {:noreply, state}
+ else
+ Logger.error(fn -> "Rootstock data fetcher task exited due to #{inspect(reason)}." end)
+ {:noreply, state}
+ end
+ end
+
+ defp fetch_blocks(%__MODULE__{db_batch_size: db_batch_size, interval: interval} = state) do
+ blocks_to_fetch = db_batch_size |> Block.blocks_without_rootstock_data_query() |> Repo.all()
+
+ if Enum.empty?(blocks_to_fetch) do
+ Logger.info("Rootstock data from old blocks are fetched.")
+
+ {:stop, :normal, state}
+ else
+ [%Block{number: max_number} | _] = blocks_to_fetch
+
+ Logger.info(
+ "Rootstock data will now be fetched for #{Enum.count(blocks_to_fetch)} blocks starting from #{max_number}."
+ )
+
+ Process.send_after(self(), :fetch_rootstock_data, interval)
+
+ {:noreply, %__MODULE__{state | blocks_to_fetch: blocks_to_fetch}}
+ end
+ end
+
+ defp fetch_reducer({:ok, {{:ok, %Blocks{blocks_params: block_params}}, blocks}}, acc) do
+ blocks_map = Map.new(blocks, fn b -> {b.number, b} end)
+
+ for block_param <- block_params,
+ block = blocks_map[block_param.number],
+ block_param.hash == to_string(block.hash) do
+ block |> Block.changeset(block_param) |> Repo.update()
+ end
+
+ acc
+ end
+
+ defp fetch_reducer({:ok, {{:error, reason}, blocks}}, acc) do
+ Logger.error("failed to fetch: " <> inspect(reason) <> ". Retrying.")
+ [blocks | acc] |> List.flatten()
+ end
+
+ defp fetch_reducer({:exit, {blocks, reason}}, acc) do
+ Logger.error("failed to fetch: " <> inspect(reason) <> ". Retrying.")
+ [blocks | acc] |> List.flatten()
+ end
+end
diff --git a/apps/indexer/lib/indexer/fetcher/token.ex b/apps/indexer/lib/indexer/fetcher/token.ex
index c04c20055aea..9d4b0dd253f3 100644
--- a/apps/indexer/lib/indexer/fetcher/token.ex
+++ b/apps/indexer/lib/indexer/fetcher/token.ex
@@ -14,12 +14,7 @@ defmodule Indexer.Fetcher.Token do
@behaviour BufferedTask
- @defaults [
- flush_interval: 300,
- max_batch_size: 1,
- max_concurrency: 10,
- task_supervisor: Indexer.Fetcher.Token.TaskSupervisor
- ]
+ @default_max_concurrency 10
@doc false
def child_spec([init_options, gen_server_options]) do
@@ -32,7 +27,7 @@ defmodule Indexer.Fetcher.Token do
end
merged_init_opts =
- @defaults
+ defaults()
|> Keyword.merge(mergeable_init_options)
|> Keyword.put(:state, state)
@@ -42,9 +37,13 @@ defmodule Indexer.Fetcher.Token do
@impl BufferedTask
def init(initial_acc, reducer, _) do
{:ok, acc} =
- Chain.stream_uncataloged_token_contract_address_hashes(initial_acc, fn address, acc ->
- reducer.(address, acc)
- end)
+ Chain.stream_uncataloged_token_contract_address_hashes(
+ initial_acc,
+ fn address, acc ->
+ reducer.(address, acc)
+ end,
+ true
+ )
acc
end
@@ -77,4 +76,13 @@ defmodule Indexer.Fetcher.Token do
{:ok, _} = Chain.update_token(token, token_params)
:ok
end
+
+ defp defaults do
+ [
+ flush_interval: 300,
+ max_batch_size: 1,
+ max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency,
+ task_supervisor: Indexer.Fetcher.Token.TaskSupervisor
+ ]
+ end
end
diff --git a/apps/indexer/lib/indexer/fetcher/token_balance.ex b/apps/indexer/lib/indexer/fetcher/token_balance.ex
index bd0f3e1302fc..29b3bad04312 100644
--- a/apps/indexer/lib/indexer/fetcher/token_balance.ex
+++ b/apps/indexer/lib/indexer/fetcher/token_balance.ex
@@ -21,17 +21,34 @@ defmodule Indexer.Fetcher.TokenBalance do
alias Explorer.Chain
alias Explorer.Chain.Hash
alias Indexer.{BufferedTask, TokenBalances, Tracer}
+ alias Indexer.Fetcher.TokenBalance.Supervisor, as: TokenBalanceSupervisor
@behaviour BufferedTask
@default_max_batch_size 100
+ @default_max_concurrency 10
+
+ @timeout :timer.minutes(10)
@max_retries 3
- @spec async_fetch([]) :: :ok
+ @spec async_fetch([
+ %{
+ token_contract_address_hash: Hash.Address.t(),
+ address_hash: Hash.Address.t(),
+ block_number: non_neg_integer(),
+ token_type: String.t(),
+ token_id: non_neg_integer()
+ }
+ ]) :: :ok
def async_fetch(token_balances) do
- formatted_params = Enum.map(token_balances, &entry/1)
- BufferedTask.buffer(__MODULE__, formatted_params, :infinity)
+ if TokenBalanceSupervisor.disabled?() do
+ :ok
+ else
+ formatted_params = Enum.map(token_balances, &entry/1)
+
+ BufferedTask.buffer(__MODULE__, formatted_params, :infinity)
+ end
end
@doc false
@@ -55,11 +72,15 @@ defmodule Indexer.Fetcher.TokenBalance do
@impl BufferedTask
def init(initial, reducer, _) do
{:ok, final} =
- Chain.stream_unfetched_token_balances(initial, fn token_balance, acc ->
- token_balance
- |> entry()
- |> reducer.(acc)
- end)
+ Chain.stream_unfetched_token_balances(
+ initial,
+ fn token_balance, acc ->
+ token_balance
+ |> entry()
+ |> reducer.(acc)
+ end,
+ true
+ )
final
end
@@ -98,17 +119,21 @@ defmodule Indexer.Fetcher.TokenBalance do
%{fetched_token_balances: fetched_token_balances, failed_token_balances: _failed_token_balances} =
1..@max_retries
|> Enum.reduce_while(%{fetched_token_balances: [], failed_token_balances: retryable_params_list}, fn _x, acc ->
- {:ok,
- %{fetched_token_balances: _fetched_token_balances, failed_token_balances: failed_token_balances} =
- token_balances} = TokenBalances.fetch_token_balances_from_blockchain(acc.failed_token_balances)
+ {:ok, %{fetched_token_balances: fetched_token_balances, failed_token_balances: failed_token_balances}} =
+ TokenBalances.fetch_token_balances_from_blockchain(acc.failed_token_balances)
+
+ all_token_balances = %{
+ fetched_token_balances: acc.fetched_token_balances ++ fetched_token_balances,
+ failed_token_balances: failed_token_balances
+ }
if Enum.empty?(failed_token_balances) do
- {:halt, token_balances}
+ {:halt, all_token_balances}
else
failed_token_balances = increase_retries_count(failed_token_balances)
token_balances_updated_retries_count =
- token_balances
+ all_token_balances
|> Map.put(:failed_token_balances, failed_token_balances)
{:cont, token_balances_updated_retries_count}
@@ -133,7 +158,7 @@ defmodule Indexer.Fetcher.TokenBalance do
address_current_token_balances: %{
params: TokenBalances.to_address_current_token_balances(formatted_token_balances_params)
},
- timeout: :infinity
+ timeout: @timeout
}
case Chain.import(import_params) do
@@ -217,7 +242,7 @@ defmodule Indexer.Fetcher.TokenBalance do
[
flush_interval: 300,
max_batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @default_max_batch_size,
- max_concurrency: 10,
+ max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency,
task_supervisor: Indexer.Fetcher.TokenBalance.TaskSupervisor
]
end
diff --git a/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex b/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex
index bddfdb809bbd..cd090f19fe36 100644
--- a/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex
+++ b/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex
@@ -6,18 +6,21 @@ defmodule Indexer.Fetcher.TokenBalanceOnDemand do
use Indexer.Fetcher
- alias Explorer.Chain
+ alias Explorer.{Chain, Repo}
alias Explorer.Chain.Address.CurrentTokenBalance
alias Explorer.Chain.Cache.BlockNumber
+ alias Explorer.Chain.Events.Publisher
alias Explorer.Chain.Hash
alias Explorer.Counters.AverageBlockTime
alias Explorer.Token.BalanceReader
alias Timex.Duration
+ require Logger
+
## Interface
- @spec trigger_fetch(Hash.t(), [CurrentTokenBalance.t()]) :: :ok
- def trigger_fetch(address_hash, current_token_balances) do
+ @spec trigger_fetch(Hash.t()) :: :ok
+ def trigger_fetch(address_hash) do
latest_block_number = latest_block_number()
case stale_balance_window(latest_block_number) do
@@ -25,17 +28,32 @@ defmodule Indexer.Fetcher.TokenBalanceOnDemand do
:current
stale_balance_window ->
- do_trigger_fetch(address_hash, current_token_balances, latest_block_number, stale_balance_window)
+ do_trigger_fetch(address_hash, latest_block_number, stale_balance_window)
end
end
+ @spec trigger_historic_fetch(
+ Hash.t(),
+ Hash.t(),
+ String.t(),
+ Decimal.t() | nil,
+ non_neg_integer()
+ ) :: {:ok, pid}
+ def trigger_historic_fetch(address_hash, contract_address_hash, token_type, token_id, block_number) do
+ Task.start(fn ->
+ do_trigger_historic_fetch(address_hash, contract_address_hash, token_type, token_id, block_number)
+ end)
+ end
+
## Implementation
- defp do_trigger_fetch(address_hash, current_token_balances, latest_block_number, stale_balance_window)
+ defp do_trigger_fetch(address_hash, latest_block_number, stale_balance_window)
when not is_nil(address_hash) do
stale_current_token_balances =
- current_token_balances
- |> Enum.filter(fn {current_token_balance, _} -> current_token_balance.block_number < stale_balance_window end)
+ address_hash
+ |> Chain.fetch_last_token_balances_include_unfetched()
+ |> delete_invalid_balances()
+ |> Enum.filter(fn current_token_balance -> current_token_balance.block_number < stale_balance_window end)
if Enum.count(stale_current_token_balances) > 0 do
fetch_and_update(latest_block_number, address_hash, stale_current_token_balances)
@@ -46,51 +64,145 @@ defmodule Indexer.Fetcher.TokenBalanceOnDemand do
:ok
end
+ defp delete_invalid_balances(current_token_balances) do
+ {invalid_balances, valid_balances} = Enum.split_with(current_token_balances, &is_nil(&1.token_type))
+ Enum.each(invalid_balances, &Repo.delete/1)
+ valid_balances
+ end
+
defp fetch_and_update(block_number, address_hash, stale_current_token_balances) do
- current_token_balances_update_params =
+ %{erc_1155: erc_1155_ctbs, other: other_ctbs, tokens: tokens} =
stale_current_token_balances
- |> Enum.map(fn {%{token_id: token_id} = stale_current_token_balance, token} ->
- stale_current_token_balances_to_fetch = [
- %{
- token_contract_address_hash: "0x" <> Base.encode16(token.contract_address_hash.bytes),
- address_hash: "0x" <> Base.encode16(address_hash.bytes),
- block_number: block_number,
- token_id: token_id && Decimal.to_integer(token_id)
- }
- ]
-
- balance_response =
- case stale_current_token_balance.token_type do
- "ERC-1155" -> BalanceReader.get_balances_of_erc_1155(stale_current_token_balances_to_fetch)
- _ -> BalanceReader.get_balances_of(stale_current_token_balances_to_fetch)
+ |> Enum.reduce(%{erc_1155: [], other: [], tokens: %{}}, fn %{token_id: token_id} = stale_current_token_balance,
+ acc ->
+ prepared_ctb = %{
+ token_contract_address_hash:
+ "0x" <> Base.encode16(stale_current_token_balance.token.contract_address_hash.bytes),
+ address_hash: "0x" <> Base.encode16(address_hash.bytes),
+ block_number: block_number,
+ token_id: token_id && Decimal.to_integer(token_id),
+ token_type: stale_current_token_balance.token_type
+ }
+
+ updated_tokens =
+ Map.put_new(
+ acc[:tokens],
+ stale_current_token_balance.token.contract_address_hash.bytes,
+ stale_current_token_balance.token
+ )
+
+ result =
+ if stale_current_token_balance.token_type == "ERC-1155" do
+ Map.put(acc, :erc_1155, [prepared_ctb | acc[:erc_1155]])
+ else
+ Map.put(acc, :other, [prepared_ctb | acc[:other]])
end
- updated_balance = balance_response[:ok]
-
- if updated_balance do
- %{}
- |> Map.put(:address_hash, stale_current_token_balance.address_hash)
- |> Map.put(:token_contract_address_hash, token.contract_address_hash)
- |> Map.put(:token_type, token.type)
- |> Map.put(:token_id, token_id)
- |> Map.put(:block_number, block_number)
- |> Map.put(:value, Decimal.new(updated_balance))
- |> Map.put(:value_fetched_at, DateTime.utc_now())
- else
- nil
- end
+ Map.put(result, :tokens, updated_tokens)
end)
+ erc_1155_ctbs_reversed = Enum.reverse(erc_1155_ctbs)
+ other_ctbs_reversed = Enum.reverse(other_ctbs)
+
+ updated_erc_1155_ctbs =
+ if Enum.count(erc_1155_ctbs_reversed) > 0 do
+ erc_1155_ctbs_reversed
+ |> BalanceReader.get_balances_of_erc_1155()
+ |> Enum.zip(erc_1155_ctbs_reversed)
+ |> Enum.map(&prepare_updated_balance(&1, block_number))
+ else
+ []
+ end
+
+ updated_other_ctbs =
+ if Enum.count(other_ctbs_reversed) > 0 do
+ other_ctbs_reversed
+ |> BalanceReader.get_balances_of()
+ |> Enum.zip(other_ctbs_reversed)
+ |> Enum.map(&prepare_updated_balance(&1, block_number))
+ else
+ []
+ end
+
filtered_current_token_balances_update_params =
- current_token_balances_update_params
+ (updated_erc_1155_ctbs ++ updated_other_ctbs)
|> Enum.filter(&(!is_nil(&1)))
- Chain.import(%{
- address_current_token_balances: %{
- params: filtered_current_token_balances_update_params
+ {:ok,
+ %{
+ address_current_token_balances: imported_ctbs
+ }} =
+ Chain.import(%{
+ address_current_token_balances: %{
+ params: filtered_current_token_balances_update_params
+ },
+ broadcast: false
+ })
+
+ Publisher.broadcast(
+ %{
+ address_current_token_balances: %{
+ address_hash: to_string(address_hash),
+ address_current_token_balances:
+ imported_ctbs
+ |> Enum.map(fn ctb -> %CurrentTokenBalance{ctb | token: tokens[ctb.token_contract_address_hash.bytes]} end)
+ }
},
- broadcast: :on_demand
- })
+ :on_demand
+ )
+ end
+
+ defp prepare_updated_balance({{:ok, updated_balance}, stale_current_token_balance}, block_number) do
+ %{}
+ |> Map.put(:address_hash, stale_current_token_balance.address_hash)
+ |> Map.put(:token_contract_address_hash, stale_current_token_balance.token_contract_address_hash)
+ |> Map.put(:token_type, stale_current_token_balance.token_type)
+ |> Map.put(:token_id, stale_current_token_balance.token_id)
+ |> Map.put(:block_number, block_number)
+ |> Map.put(:value, Decimal.new(updated_balance))
+ |> Map.put(:value_fetched_at, DateTime.utc_now())
+ end
+
+ defp prepare_updated_balance({{:error, error}, _ctb}, _block_number) do
+ Logger.warn(fn -> ["Error on updating current token balance: ", inspect(error)] end)
+ nil
+ end
+
+ defp do_trigger_historic_fetch(address_hash, contract_address_hash, token_type, token_id, block_number) do
+ request = %{
+ token_contract_address_hash: to_string(contract_address_hash),
+ address_hash: to_string(address_hash),
+ block_number: block_number,
+ token_id: token_id && Decimal.to_integer(token_id)
+ }
+
+ balance_response =
+ case token_type do
+ "ERC-1155" -> BalanceReader.get_balances_of_erc_1155([request])
+ _ -> BalanceReader.get_balances_of([request])
+ end
+
+ balance = balance_response[:ok]
+
+ if balance do
+ %{
+ address_token_balances: %{
+ params: [
+ %{
+ address_hash: address_hash,
+ token_contract_address_hash: contract_address_hash,
+ token_type: token_type,
+ token_id: token_id,
+ block_number: block_number,
+ value: Decimal.new(balance),
+ value_fetched_at: DateTime.utc_now()
+ }
+ ]
+ },
+ broadcast: :on_demand
+ }
+ |> Chain.import()
+ end
end
defp latest_block_number do
diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex b/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex
index 57bc8ca37f58..792f92c85ada 100644
--- a/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex
+++ b/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex
@@ -3,45 +3,192 @@ defmodule Indexer.Fetcher.TokenInstance.Helper do
Common functions for Indexer.Fetcher.TokenInstance fetchers
"""
alias Explorer.Chain
- alias Explorer.Chain.{Hash, Token.Instance}
- alias Explorer.Token.InstanceMetadataRetriever
-
- @spec fetch_instance(Hash.Address.t(), Decimal.t() | non_neg_integer()) :: {:ok, Instance.t()}
- def fetch_instance(token_contract_address_hash, token_id) do
- token_id = prepare_token_id(token_id)
-
- case InstanceMetadataRetriever.fetch_metadata(to_string(token_contract_address_hash), token_id) do
- {:ok, %{metadata: metadata}} ->
- params = %{
- token_id: token_id,
- token_contract_address_hash: token_contract_address_hash,
- metadata: metadata,
- error: nil
+ alias Explorer.SmartContract.Reader
+ alias Indexer.Fetcher.TokenInstance.MetadataRetriever
+
+ require Logger
+
+ @cryptokitties_address_hash "0x06012c8cf97bead5deae237070f9587f8e7a266d"
+
+ @token_uri "c87b56dd"
+ @uri "0e89341c"
+
+ @erc_721_1155_abi [
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [
+ %{"type" => "string", "name" => ""}
+ ],
+ "name" => "tokenURI",
+ "inputs" => [
+ %{
+ "type" => "uint256",
+ "name" => "_tokenId"
}
+ ],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [
+ %{
+ "type" => "string",
+ "name" => "",
+ "internalType" => "string"
+ }
+ ],
+ "name" => "uri",
+ "inputs" => [
+ %{
+ "type" => "uint256",
+ "name" => "_id",
+ "internalType" => "uint256"
+ }
+ ],
+ "constant" => true
+ }
+ ]
+
+ @spec batch_fetch_instances([%{}]) :: list()
+ def batch_fetch_instances(token_instances) do
+ token_instances =
+ Enum.map(token_instances, fn
+ %{contract_address_hash: hash, token_id: token_id} -> {hash, token_id}
+ {_, _} = tuple -> tuple
+ end)
- {:ok, _result} = Chain.upsert_token_instance(params)
+ splitted =
+ Enum.group_by(token_instances, fn {contract_address_hash, _token_id} ->
+ to_string(contract_address_hash) == @cryptokitties_address_hash
+ end)
- {:ok, %{error: error}} ->
- upsert_token_instance_with_error(token_id, token_contract_address_hash, error)
+ cryptokitties =
+ (splitted[true] || [])
+ |> Enum.map(fn {contract_address_hash, token_id} ->
+ {{:ok, ["https://api.cryptokitties.co/kitties/{id}"]}, to_string(token_id), contract_address_hash, token_id}
+ end)
- {:error_code, code} ->
- upsert_token_instance_with_error(token_id, token_contract_address_hash, "request error: #{code}")
+ other = splitted[false] || []
- {:error, reason} ->
- upsert_token_instance_with_error(token_id, token_contract_address_hash, reason)
- end
+ token_types_map =
+ Enum.reduce(other, %{}, fn {contract_address_hash, _token_id}, acc ->
+ address_hash_string = to_string(contract_address_hash)
+
+ Map.put_new(acc, address_hash_string, Chain.get_token_type(contract_address_hash))
+ end)
+
+ contract_results =
+ (other
+ |> Enum.map(fn {contract_address_hash, token_id} ->
+ token_id = prepare_token_id(token_id)
+ contract_address_hash_string = to_string(contract_address_hash)
+
+ prepare_request(token_types_map[contract_address_hash_string], contract_address_hash_string, token_id)
+ end)
+ |> Reader.query_contracts(@erc_721_1155_abi, [], false)
+ |> Enum.zip_reduce(other, [], fn result, {contract_address_hash, token_id}, acc ->
+ token_id = prepare_token_id(token_id)
+
+ [
+ {result, normalize_token_id(token_types_map[to_string(contract_address_hash)], token_id),
+ contract_address_hash, token_id}
+ | acc
+ ]
+ end)
+ |> Enum.reverse()) ++
+ cryptokitties
+
+ contract_results
+ |> Enum.map(fn {result, normalized_token_id, _contract_address_hash, _token_id} ->
+ Task.async(fn -> MetadataRetriever.fetch_json(result, normalized_token_id) end)
+ end)
+ |> Task.yield_many(:infinity)
+ |> Enum.zip(contract_results)
+ |> Enum.map(fn {{_task, res}, {_result, _normalized_token_id, contract_address_hash, token_id}} ->
+ insert_params =
+ case res do
+ {:ok, result} ->
+ result_to_insert_params(result, contract_address_hash, token_id)
+
+ {:exit, reason} ->
+ result_to_insert_params(
+ {:error, MetadataRetriever.truncate_error("Terminated:" <> inspect(reason))},
+ contract_address_hash,
+ token_id
+ )
+ end
+
+ upsert_with_rescue(insert_params, token_id, contract_address_hash)
+ end)
end
defp prepare_token_id(%Decimal{} = token_id), do: Decimal.to_integer(token_id)
defp prepare_token_id(token_id), do: token_id
- defp upsert_token_instance_with_error(token_id, token_contract_address_hash, error) do
- params = %{
+ defp prepare_request("ERC-721", contract_address_hash_string, token_id) do
+ %{
+ contract_address: contract_address_hash_string,
+ method_id: @token_uri,
+ args: [token_id],
+ block_number: nil
+ }
+ end
+
+ defp prepare_request(_token_type, contract_address_hash_string, token_id) do
+ %{
+ contract_address: contract_address_hash_string,
+ method_id: @uri,
+ args: [token_id],
+ block_number: nil
+ }
+ end
+
+ defp normalize_token_id("ERC-721", _token_id), do: nil
+
+ defp normalize_token_id(_token_type, token_id),
+ do: token_id |> Integer.to_string(16) |> String.downcase() |> String.pad_leading(64, "0")
+
+ defp result_to_insert_params({:ok, %{metadata: metadata}}, token_contract_address_hash, token_id) do
+ %{
+ token_id: token_id,
+ token_contract_address_hash: token_contract_address_hash,
+ metadata: metadata,
+ error: nil
+ }
+ end
+
+ defp result_to_insert_params({:error_code, code}, token_contract_address_hash, token_id),
+ do: token_instance_map_with_error(token_id, token_contract_address_hash, "request error: #{code}")
+
+ defp result_to_insert_params({:error, reason}, token_contract_address_hash, token_id),
+ do: token_instance_map_with_error(token_id, token_contract_address_hash, reason)
+
+ defp token_instance_map_with_error(token_id, token_contract_address_hash, error) do
+ %{
token_id: token_id,
token_contract_address_hash: token_contract_address_hash,
error: error
}
+ end
- {:ok, _result} = Chain.upsert_token_instance(params)
+ defp upsert_with_rescue(insert_params, token_id, token_contract_address_hash, retrying? \\ false) do
+ Chain.upsert_token_instance(insert_params)
+ rescue
+ error in Postgrex.Error ->
+ if retrying? do
+ Logger.warn(["Failed to upsert token instance: #{inspect(error)}"], fetcher: :token_instances)
+ nil
+ else
+ token_id
+ |> token_instance_map_with_error(
+ token_contract_address_hash,
+ MetadataRetriever.truncate_error(inspect(error.postgres.code))
+ )
+ |> upsert_with_rescue(token_id, token_contract_address_hash, true)
+ end
end
end
diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/legacy_sanitize.ex b/apps/indexer/lib/indexer/fetcher/token_instance/legacy_sanitize.ex
new file mode 100644
index 000000000000..391353ba3788
--- /dev/null
+++ b/apps/indexer/lib/indexer/fetcher/token_instance/legacy_sanitize.ex
@@ -0,0 +1,50 @@
+defmodule Indexer.Fetcher.TokenInstance.LegacySanitize do
+ @moduledoc """
+ This fetcher is stands for creating token instances which wasn't inserted yet and index meta for them.
+ Legacy is because now we token instances inserted on block import and this fetcher is only for historical and unfetched for some reasons data
+ """
+
+ use GenServer, restart: :transient
+
+ alias Explorer.Chain.Token.Instance
+ alias Explorer.Repo
+
+ import Indexer.Fetcher.TokenInstance.Helper
+
+ def start_link(_) do
+ concurrency = Application.get_env(:indexer, __MODULE__)[:concurrency]
+ batch_size = Application.get_env(:indexer, __MODULE__)[:batch_size]
+ GenServer.start_link(__MODULE__, %{concurrency: concurrency, batch_size: batch_size}, name: __MODULE__)
+ end
+
+ @impl true
+ def init(opts) do
+ GenServer.cast(__MODULE__, :backfill)
+
+ {:ok, opts}
+ end
+
+ @impl true
+ def handle_cast(:backfill, %{concurrency: concurrency, batch_size: batch_size} = state) do
+ instances_to_fetch =
+ (concurrency * batch_size)
+ |> Instance.not_inserted_token_instances_query()
+ |> Repo.all()
+
+ if Enum.empty?(instances_to_fetch) do
+ {:stop, :normal, state}
+ else
+ instances_to_fetch
+ |> Enum.uniq()
+ |> Enum.chunk_every(batch_size)
+ |> Enum.map(&process_batch/1)
+ |> Task.await_many(:infinity)
+
+ GenServer.cast(__MODULE__, :backfill)
+
+ {:noreply, state}
+ end
+ end
+
+ defp process_batch(batch), do: Task.async(fn -> batch_fetch_instances(batch) end)
+end
diff --git a/apps/explorer/lib/explorer/token/instance_metadata_retriever.ex b/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex
similarity index 64%
rename from apps/explorer/lib/explorer/token/instance_metadata_retriever.ex
rename to apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex
index 0594e401b564..a4812fb332a5 100644
--- a/apps/explorer/lib/explorer/token/instance_metadata_retriever.ex
+++ b/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex
@@ -1,66 +1,17 @@
-defmodule Explorer.Token.InstanceMetadataRetriever do
+defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do
@moduledoc """
- Fetches ERC721 token instance metadata.
+ Fetches ERC-721 & ERC-1155 token instance metadata.
"""
require Logger
+ alias Explorer.Helper, as: ExplorerHelper
alias Explorer.SmartContract.Reader
alias HTTPoison.{Error, Response}
- @token_uri "c87b56dd"
-
- @abi [
- %{
- "type" => "function",
- "stateMutability" => "view",
- "payable" => false,
- "outputs" => [
- %{"type" => "string", "name" => ""}
- ],
- "name" => "tokenURI",
- "inputs" => [
- %{
- "type" => "uint256",
- "name" => "_tokenId"
- }
- ],
- "constant" => true
- }
- ]
-
- @uri "0e89341c"
-
- @abi_uri [
- %{
- "type" => "function",
- "stateMutability" => "view",
- "payable" => false,
- "outputs" => [
- %{
- "type" => "string",
- "name" => "",
- "internalType" => "string"
- }
- ],
- "name" => "uri",
- "inputs" => [
- %{
- "type" => "uint256",
- "name" => "_id",
- "internalType" => "uint256"
- }
- ],
- "constant" => true
- }
- ]
-
- @cryptokitties_address_hash "0x06012c8cf97bead5deae237070f9587f8e7a266d"
-
@no_uri_error "no uri"
@vm_execution_error "VM execution error"
@ipfs_protocol "ipfs://"
- @ipfs_link "https://ipfs.io/ipfs/"
# https://eips.ethereum.org/EIPS/eip-1155#metadata
@erc1155_token_id_placeholder "{id}"
@@ -69,56 +20,40 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
@ignored_hosts ["localhost", "127.0.0.1", "0.0.0.0", "", nil]
- def fetch_metadata(unquote(@cryptokitties_address_hash), token_id) do
- %{@token_uri => {:ok, ["https://api.cryptokitties.co/kitties/{id}"]}}
- |> fetch_json(to_string(token_id))
- end
-
- def fetch_metadata(contract_address_hash, token_id) do
- # c87b56dd = keccak256(tokenURI(uint256))
- contract_functions = %{@token_uri => [token_id]}
-
- res =
- contract_address_hash
- |> query_contract(contract_functions, @abi)
- |> fetch_json()
-
- if res == {:ok, %{error: @vm_execution_error}} do
- hex_normalized_token_id = token_id |> Integer.to_string(16) |> String.downcase() |> String.pad_leading(64, "0")
-
- contract_functions_uri = %{@uri => [token_id]}
+ defp ipfs_link do
+ link =
+ :indexer
+ |> Application.get_env(:ipfs_gateway_url)
+ |> String.trim_trailing("/")
- contract_address_hash
- |> query_contract(contract_functions_uri, @abi_uri)
- |> fetch_json(hex_normalized_token_id)
- else
- res
- end
+ link <> "/"
end
def query_contract(contract_address_hash, contract_functions, abi) do
Reader.query_contract(contract_address_hash, abi, contract_functions, false)
end
+ @doc """
+ Fetch/parse metadata using smart-contract's response
+ """
+ @spec fetch_json(any, binary() | nil) :: {:error, binary} | {:error_code, any} | {:ok, %{metadata: any}}
def fetch_json(uri, hex_token_id \\ nil)
- def fetch_json(uri, _hex_token_id) when uri in [%{@token_uri => {:ok, [""]}}, %{@uri => {:ok, [""]}}] do
- {:ok, %{error: @no_uri_error}}
+ def fetch_json(uri, _hex_token_id) when uri in [{:ok, [""]}, {:ok, [""]}] do
+ {:error, @no_uri_error}
end
- def fetch_json(%{@token_uri => uri}, hex_token_id) do
- fetch_json_from_uri(uri, hex_token_id)
- end
-
- def fetch_json(%{@uri => uri}, hex_token_id) do
+ def fetch_json(uri, hex_token_id) do
fetch_json_from_uri(uri, hex_token_id)
end
defp fetch_json_from_uri({:error, error}, _hex_token_id) do
+ error = to_string(error)
+
if error =~ "execution reverted" or error =~ @vm_execution_error do
- {:ok, %{error: @vm_execution_error}}
+ {:error, @vm_execution_error}
else
- Logger.debug(["Unknown metadata format error #{inspect(error)}."], fetcher: :token_instances)
+ Logger.warn(["Unknown metadata format error #{inspect(error)}."], fetcher: :token_instances)
# truncate error since it will be stored in DB
{:error, truncate_error(error)}
@@ -128,9 +63,9 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
# CIDv0 IPFS links # https://docs.ipfs.tech/concepts/content-addressing/#version-0-v0
defp fetch_json_from_uri({:ok, ["Qm" <> _ = result]}, hex_token_id) do
if String.length(result) == 46 do
- fetch_json_from_uri({:ok, [@ipfs_link <> result]}, hex_token_id)
+ fetch_json_from_uri({:ok, [ipfs_link() <> result]}, hex_token_id)
else
- Logger.debug(["Unknown metadata format result #{inspect(result)}."], fetcher: :token_instances)
+ Logger.warn(["Unknown metadata format result #{inspect(result)}."], fetcher: :token_instances)
{:error, truncate_error(result)}
end
@@ -155,7 +90,7 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
fetch_json_from_uri({:ok, [decoded_json]}, hex_token_id)
rescue
e ->
- Logger.debug(["Unknown metadata format #{inspect(json)}.", Exception.format(:error, e, __STACKTRACE__)],
+ Logger.warn(["Unknown metadata format #{inspect(json)}.", Exception.format(:error, e, __STACKTRACE__)],
fetcher: :token_instances
)
@@ -172,7 +107,7 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
end
rescue
e ->
- Logger.debug(
+ Logger.warn(
[
"Unknown metadata format base64 #{inspect(base64_encoded_json)}.",
Exception.format(:error, e, __STACKTRACE__)
@@ -187,17 +122,21 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
fetch_from_ipfs(right, hex_token_id)
end
+ defp fetch_json_from_uri({:ok, ["ipfs/" <> right]}, hex_token_id) do
+ fetch_from_ipfs(right, hex_token_id)
+ end
+
defp fetch_json_from_uri({:ok, [@ipfs_protocol <> right]}, hex_token_id) do
fetch_from_ipfs(right, hex_token_id)
end
defp fetch_json_from_uri({:ok, [json]}, hex_token_id) do
- {:ok, json} = decode_json(json)
+ json = ExplorerHelper.decode_json(json)
check_type(json, hex_token_id)
rescue
e ->
- Logger.debug(["Unknown metadata format #{inspect(json)}.", Exception.format(:error, e, __STACKTRACE__)],
+ Logger.warn(["Unknown metadata format #{inspect(json)}.", Exception.format(:error, e, __STACKTRACE__)],
fetcher: :token_instances
)
@@ -205,13 +144,13 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
end
defp fetch_json_from_uri(uri, _hex_token_id) do
- Logger.debug(["Unknown metadata uri format #{inspect(uri)}."], fetcher: :token_instances)
+ Logger.warn(["Unknown metadata uri format #{inspect(uri)}."], fetcher: :token_instances)
{:error, "unknown metadata uri format"}
end
defp fetch_from_ipfs(ipfs_uid, hex_token_id) do
- ipfs_url = @ipfs_link <> ipfs_uid
+ ipfs_url = ipfs_link() <> ipfs_uid
fetch_metadata_inner(ipfs_url, hex_token_id)
end
@@ -220,7 +159,7 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
fetch_metadata_from_uri(prepared_uri, hex_token_id)
rescue
e ->
- Logger.debug(
+ Logger.warn(
["Could not prepare token uri #{inspect(uri)}.", Exception.format(:error, e, __STACKTRACE__)],
fetcher: :token_instances
)
@@ -240,9 +179,9 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
def fetch_metadata_from_uri_inner(uri, hex_token_id) do
case Application.get_env(:explorer, :http_adapter).get(uri, [],
- timeout: 60_000,
- recv_timeout: 60_000,
- follow_redirect: true
+ recv_timeout: 30_000,
+ follow_redirect: true,
+ hackney: [pool: :token_instance_fetcher]
) do
{:ok, %Response{body: body, status_code: 200, headers: headers}} ->
content_type = get_content_type_from_headers(headers)
@@ -250,7 +189,7 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
check_content_type(content_type, uri, hex_token_id, body)
{:ok, %Response{body: body, status_code: code}} ->
- Logger.debug(
+ Logger.warn(
["Request to token uri: #{inspect(uri)} failed with code #{code}. Body:", inspect(body)],
fetcher: :token_instances
)
@@ -258,16 +197,16 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
{:error_code, code}
{:error, %Error{reason: reason}} ->
- Logger.debug(
+ Logger.warn(
["Request to token uri failed: #{inspect(uri)}.", inspect(reason)],
fetcher: :token_instances
)
- {:error, reason |> inspect(reason) |> truncate_error()}
+ {:error, reason |> inspect() |> truncate_error()}
end
rescue
e ->
- Logger.debug(
+ Logger.warn(
["Could not send request to token uri #{inspect(uri)}.", Exception.format(:error, e, __STACKTRACE__)],
fetcher: :token_instances
)
@@ -284,7 +223,7 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
check_type(json, nil)
else
- {:ok, json} = decode_json(body)
+ json = ExplorerHelper.decode_json(body)
check_type(json, hex_token_id)
end
@@ -307,16 +246,6 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
content_type && String.starts_with?(content_type, "video/")
end
- defp decode_json(body) do
- if String.valid?(body) do
- Jason.decode(body)
- else
- body
- |> :unicode.characters_to_binary(:latin1)
- |> Jason.decode()
- end
- end
-
defp check_type(json, nil) when is_map(json) do
{:ok, %{metadata: json}}
end
@@ -347,5 +276,9 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
String.replace(token_uri, @erc1155_token_id_placeholder, hex_token_id)
end
- defp truncate_error(error), do: String.slice(error, 0, @max_error_length)
+ @doc """
+ Truncate error string to @max_error_length symbols
+ """
+ @spec truncate_error(binary()) :: binary()
+ def truncate_error(error), do: String.slice(error, 0, @max_error_length)
end
diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/realtime.ex b/apps/indexer/lib/indexer/fetcher/token_instance/realtime.ex
index 3f82494b55b6..14375b16018a 100644
--- a/apps/indexer/lib/indexer/fetcher/token_instance/realtime.ex
+++ b/apps/indexer/lib/indexer/fetcher/token_instance/realtime.ex
@@ -32,10 +32,12 @@ defmodule Indexer.Fetcher.TokenInstance.Realtime do
end
@impl BufferedTask
- def run([%{contract_address_hash: hash, token_id: token_id}], _json_rpc_named_arguments) do
- if not Chain.token_instance_exists?(token_id, hash) do
- fetch_instance(hash, token_id)
- end
+ def run(token_instances, _) when is_list(token_instances) do
+ token_instances
+ |> Enum.filter(fn %{contract_address_hash: hash, token_id: token_id} ->
+ Chain.token_instance_with_unfetched_metadata?(token_id, hash)
+ end)
+ |> batch_fetch_instances()
:ok
end
@@ -75,7 +77,7 @@ defmodule Indexer.Fetcher.TokenInstance.Realtime do
[
flush_interval: 100,
max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency,
- max_batch_size: @default_max_batch_size,
+ max_batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @default_max_batch_size,
poll: false,
task_supervisor: __MODULE__.TaskSupervisor
]
diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/retry.ex b/apps/indexer/lib/indexer/fetcher/token_instance/retry.ex
index 0ea6562cbd40..a09bce459698 100644
--- a/apps/indexer/lib/indexer/fetcher/token_instance/retry.ex
+++ b/apps/indexer/lib/indexer/fetcher/token_instance/retry.ex
@@ -13,7 +13,7 @@ defmodule Indexer.Fetcher.TokenInstance.Retry do
@behaviour BufferedTask
- @default_max_batch_size 1
+ @default_max_batch_size 10
@default_max_concurrency 10
@doc false
@@ -29,22 +29,27 @@ defmodule Indexer.Fetcher.TokenInstance.Retry do
@impl BufferedTask
def init(initial_acc, reducer, _) do
{:ok, acc} =
- Chain.stream_token_instances_with_error(initial_acc, fn data, acc ->
- reducer.(data, acc)
- end)
+ Chain.stream_token_instances_with_error(
+ initial_acc,
+ fn data, acc ->
+ reducer.(data, acc)
+ end
+ )
acc
end
@impl BufferedTask
- def run([%{contract_address_hash: hash, token_id: token_id, updated_at: updated_at}], _json_rpc_named_arguments) do
+ def run(token_instances, _json_rpc_named_arguments) when is_list(token_instances) do
refetch_interval = Application.get_env(:indexer, __MODULE__)[:refetch_interval]
- if updated_at
- |> DateTime.add(refetch_interval, :millisecond)
- |> DateTime.compare(DateTime.utc_now()) != :gt do
- fetch_instance(hash, token_id)
- end
+ token_instances
+ |> Enum.filter(fn %{contract_address_hash: _hash, token_id: _token_id, updated_at: updated_at} ->
+ updated_at
+ |> DateTime.add(refetch_interval, :millisecond)
+ |> DateTime.compare(DateTime.utc_now()) != :gt
+ end)
+ |> batch_fetch_instances()
:ok
end
@@ -53,8 +58,7 @@ defmodule Indexer.Fetcher.TokenInstance.Retry do
[
flush_interval: :timer.minutes(10),
max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency,
- max_batch_size: @default_max_batch_size,
- poll: true,
+ max_batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @default_max_batch_size,
task_supervisor: __MODULE__.TaskSupervisor
]
end
diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/sanitize.ex b/apps/indexer/lib/indexer/fetcher/token_instance/sanitize.ex
index 31c594ec0055..f2cc5fa71d96 100644
--- a/apps/indexer/lib/indexer/fetcher/token_instance/sanitize.ex
+++ b/apps/indexer/lib/indexer/fetcher/token_instance/sanitize.ex
@@ -13,7 +13,7 @@ defmodule Indexer.Fetcher.TokenInstance.Sanitize do
@behaviour BufferedTask
- @default_max_batch_size 1
+ @default_max_batch_size 10
@default_max_concurrency 10
@doc false
def child_spec([init_options, gen_server_options]) do
@@ -28,7 +28,7 @@ defmodule Indexer.Fetcher.TokenInstance.Sanitize do
@impl BufferedTask
def init(initial_acc, reducer, _) do
{:ok, acc} =
- Chain.stream_unfetched_token_instances(initial_acc, fn data, acc ->
+ Chain.stream_token_instances_with_unfetched_metadata(initial_acc, fn data, acc ->
reducer.(data, acc)
end)
@@ -36,10 +36,12 @@ defmodule Indexer.Fetcher.TokenInstance.Sanitize do
end
@impl BufferedTask
- def run([%{contract_address_hash: hash, token_id: token_id}], _json_rpc_named_arguments) do
- if not Chain.token_instance_exists?(token_id, hash) do
- fetch_instance(hash, token_id)
- end
+ def run(token_instances, _) when is_list(token_instances) do
+ token_instances
+ |> Enum.filter(fn %{contract_address_hash: hash, token_id: token_id} ->
+ Chain.token_instance_with_unfetched_metadata?(token_id, hash)
+ end)
+ |> batch_fetch_instances()
:ok
end
@@ -48,7 +50,7 @@ defmodule Indexer.Fetcher.TokenInstance.Sanitize do
[
flush_interval: :infinity,
max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency,
- max_batch_size: @default_max_batch_size,
+ max_batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @default_max_batch_size,
poll: false,
task_supervisor: __MODULE__.TaskSupervisor
]
diff --git a/apps/indexer/lib/indexer/fetcher/token_total_supply_updater.ex b/apps/indexer/lib/indexer/fetcher/token_total_supply_updater.ex
new file mode 100644
index 000000000000..407e7407d7c6
--- /dev/null
+++ b/apps/indexer/lib/indexer/fetcher/token_total_supply_updater.ex
@@ -0,0 +1,69 @@
+defmodule Indexer.Fetcher.TokenTotalSupplyUpdater do
+ @moduledoc """
+ Periodically updates tokens total_supply
+ """
+
+ use GenServer
+
+ alias Explorer.{Chain, Repo}
+ alias Explorer.Chain.Token
+ alias Explorer.Counters.AverageBlockTime
+ alias Explorer.Token.MetadataRetriever
+ alias Timex.Duration
+
+ @default_update_interval :timer.seconds(10)
+
+ def start_link(_) do
+ GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
+ end
+
+ def init(_) do
+ schedule_next_update()
+
+ {:ok, []}
+ end
+
+ def add_tokens(contract_address_hashes) do
+ GenServer.cast(__MODULE__, {:add_tokens, contract_address_hashes})
+ end
+
+ def handle_cast({:add_tokens, contract_address_hashes}, state) do
+ {:noreply, Enum.uniq(List.wrap(contract_address_hashes) ++ state)}
+ end
+
+ def handle_info(:update, contract_address_hashes) do
+ Enum.each(contract_address_hashes, &update_token/1)
+
+ schedule_next_update()
+
+ {:noreply, []}
+ end
+
+ defp schedule_next_update do
+ update_interval =
+ case AverageBlockTime.average_block_time() do
+ {:error, :disabled} -> @default_update_interval
+ block_time -> round(Duration.to_milliseconds(block_time))
+ end
+
+ Process.send_after(self(), :update, update_interval)
+ end
+
+ defp update_token(nil), do: :ok
+
+ defp update_token(address_hash_string) do
+ {:ok, address_hash} = Chain.string_to_address_hash(address_hash_string)
+
+ token = Repo.get_by(Token, contract_address_hash: address_hash)
+
+ if token && !token.skip_metadata do
+ token_params = MetadataRetriever.get_total_supply_of(address_hash_string)
+
+ if token_params !== %{} do
+ {:ok, _} = Chain.update_token(token, token_params)
+ end
+ end
+
+ :ok
+ end
+end
diff --git a/apps/indexer/lib/indexer/fetcher/token_updater.ex b/apps/indexer/lib/indexer/fetcher/token_updater.ex
index 098bf8d2d77e..58c35c6246f0 100644
--- a/apps/indexer/lib/indexer/fetcher/token_updater.ex
+++ b/apps/indexer/lib/indexer/fetcher/token_updater.ex
@@ -52,7 +52,7 @@ defmodule Indexer.Fetcher.TokenUpdater do
|> Duration.to_minutes()
|> trunc()
- {:ok, tokens} = Chain.stream_cataloged_token_contract_address_hashes(initial, reducer, interval_in_minutes)
+ {:ok, tokens} = Chain.stream_cataloged_token_contract_address_hashes(initial, reducer, interval_in_minutes, true)
tokens
end
diff --git a/apps/indexer/lib/indexer/fetcher/transaction_action.ex b/apps/indexer/lib/indexer/fetcher/transaction_action.ex
index d12dc3dee164..6fddbf52a736 100644
--- a/apps/indexer/lib/indexer/fetcher/transaction_action.ex
+++ b/apps/indexer/lib/indexer/fetcher/transaction_action.ex
@@ -15,7 +15,7 @@ defmodule Indexer.Fetcher.TransactionAction do
alias Explorer.{Chain, Repo}
alias Explorer.Helper, as: ExplorerHelper
- alias Explorer.Chain.{Log, TransactionAction}
+ alias Explorer.Chain.{Block, Log, TransactionAction}
alias Indexer.Transform.{Addresses, TransactionActions}
@stage_first_block "tx_action_first_block"
@@ -87,8 +87,10 @@ defmodule Indexer.Fetcher.TransactionAction do
if reason === :normal do
{:noreply, %__MODULE__{state | task: nil}}
else
+ logger_metadata = Logger.metadata()
Logger.metadata(fetcher: :transaction_action)
Logger.error(fn -> "Transaction action fetcher task exited due to #{inspect(reason)}. Rerunning..." end)
+ Logger.reset_metadata(logger_metadata)
{:noreply, run_fetch(%__MODULE__{state | next_block: get_stage_block(@stage_next_block)})}
end
end
@@ -108,6 +110,7 @@ defmodule Indexer.Fetcher.TransactionAction do
pid: pid
} = _state
) do
+ logger_metadata = Logger.metadata()
Logger.metadata(fetcher: :transaction_action)
block_range = Range.new(next_block, first_block, -1)
@@ -117,6 +120,8 @@ defmodule Indexer.Fetcher.TransactionAction do
query =
from(
log in Log,
+ inner_join: b in Block,
+ on: b.hash == log.block_hash and b.consensus == true,
where: log.block_number == ^block_number,
select: log
)
@@ -183,51 +188,59 @@ defmodule Indexer.Fetcher.TransactionAction do
Process.send(pid, :stop_server, [])
+ Logger.reset_metadata(logger_metadata)
+
:ok
end
defp init_fetching(opts, first_block, last_block) do
+ logger_metadata = Logger.metadata()
Logger.metadata(fetcher: :transaction_action)
first_block = ExplorerHelper.parse_integer(first_block)
last_block = ExplorerHelper.parse_integer(last_block)
- if is_nil(first_block) or is_nil(last_block) or first_block <= 0 or last_block <= 0 or first_block > last_block do
- {:stop, "Correct block range must be provided to #{__MODULE__}."}
- else
- max_block_number = Chain.fetch_max_block_number()
-
- if last_block > max_block_number do
- Logger.warning(
- "Note, that the last block number (#{last_block}) provided to #{__MODULE__} exceeds max block number available in DB (#{max_block_number})."
- )
+ return =
+ if is_nil(first_block) or is_nil(last_block) or first_block <= 0 or last_block <= 0 or first_block > last_block do
+ {:stop, "Correct block range must be provided to #{__MODULE__}."}
+ else
+ max_block_number = Chain.fetch_max_block_number()
+
+ if last_block > max_block_number do
+ Logger.warning(
+ "Note, that the last block number (#{last_block}) provided to #{__MODULE__} exceeds max block number available in DB (#{max_block_number})."
+ )
+ end
+
+ supported_protocols =
+ TransactionAction.supported_protocols()
+ |> Enum.map(&Atom.to_string(&1))
+
+ protocols =
+ opts
+ |> Keyword.get(:reindex_protocols, "")
+ |> String.trim()
+ |> String.split(",")
+ |> Enum.map(&String.trim(&1))
+ |> Enum.filter(&Enum.member?(supported_protocols, &1))
+
+ next_block = get_next_block(first_block, last_block, protocols)
+
+ state =
+ %__MODULE__{
+ first_block: first_block,
+ next_block: next_block,
+ last_block: last_block,
+ protocols: protocols
+ }
+ |> run_fetch()
+
+ {:ok, state}
end
- supported_protocols =
- TransactionAction.supported_protocols()
- |> Enum.map(&Atom.to_string(&1))
-
- protocols =
- opts
- |> Keyword.get(:reindex_protocols, "")
- |> String.trim()
- |> String.split(",")
- |> Enum.map(&String.trim(&1))
- |> Enum.filter(&Enum.member?(supported_protocols, &1))
-
- next_block = get_next_block(first_block, last_block, protocols)
-
- state =
- %__MODULE__{
- first_block: first_block,
- next_block: next_block,
- last_block: last_block,
- protocols: protocols
- }
- |> run_fetch()
-
- {:ok, state}
- end
+ Logger.reset_metadata(logger_metadata)
+
+ return
end
defp get_next_block(first_block, last_block, protocols) do
diff --git a/apps/indexer/lib/indexer/fetcher/uncle_block.ex b/apps/indexer/lib/indexer/fetcher/uncle_block.ex
index eea56f5445a4..564d428f7519 100644
--- a/apps/indexer/lib/indexer/fetcher/uncle_block.ex
+++ b/apps/indexer/lib/indexer/fetcher/uncle_block.ex
@@ -65,11 +65,15 @@ defmodule Indexer.Fetcher.UncleBlock do
@impl BufferedTask
def init(initial, reducer, _) do
{:ok, final} =
- Chain.stream_unfetched_uncles(initial, fn uncle, acc ->
- uncle
- |> entry()
- |> reducer.(acc)
- end)
+ Chain.stream_unfetched_uncles(
+ initial,
+ fn uncle, acc ->
+ uncle
+ |> entry()
+ |> reducer.(acc)
+ end,
+ true
+ )
final
end
diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/transaction_batch.ex b/apps/indexer/lib/indexer/fetcher/zkevm/transaction_batch.ex
new file mode 100644
index 000000000000..d59da1203e63
--- /dev/null
+++ b/apps/indexer/lib/indexer/fetcher/zkevm/transaction_batch.ex
@@ -0,0 +1,332 @@
+defmodule Indexer.Fetcher.Zkevm.TransactionBatch do
+ @moduledoc """
+ Fills zkevm_transaction_batches DB table.
+ """
+
+ use GenServer
+ use Indexer.Fetcher
+
+ require Logger
+
+ import EthereumJSONRPC, only: [integer_to_quantity: 1, json_rpc: 2, quantity_to_integer: 1]
+
+ alias Explorer.Chain
+ alias Explorer.Chain.Events.Publisher
+ alias Explorer.Chain.Zkevm.Reader
+
+ @zero_hash "0000000000000000000000000000000000000000000000000000000000000000"
+
+ def child_spec(start_link_arguments) do
+ spec = %{
+ id: __MODULE__,
+ start: {__MODULE__, :start_link, start_link_arguments},
+ restart: :transient,
+ type: :worker
+ }
+
+ Supervisor.child_spec(spec, [])
+ end
+
+ def start_link(args, gen_server_options \\ []) do
+ GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__))
+ end
+
+ @impl GenServer
+ def init(args) do
+ Logger.metadata(fetcher: :zkevm_transaction_batches)
+
+ config = Application.get_all_env(:indexer)[Indexer.Fetcher.Zkevm.TransactionBatch]
+ chunk_size = config[:chunk_size]
+ recheck_interval = config[:recheck_interval]
+
+ Process.send(self(), :continue, [])
+
+ {:ok,
+ %{
+ chunk_size: chunk_size,
+ json_rpc_named_arguments: args[:json_rpc_named_arguments],
+ prev_latest_batch_number: 0,
+ prev_virtual_batch_number: 0,
+ prev_verified_batch_number: 0,
+ recheck_interval: recheck_interval
+ }}
+ end
+
+ @impl GenServer
+ def handle_info(
+ :continue,
+ %{
+ chunk_size: chunk_size,
+ json_rpc_named_arguments: json_rpc_named_arguments,
+ prev_latest_batch_number: prev_latest_batch_number,
+ prev_virtual_batch_number: prev_virtual_batch_number,
+ prev_verified_batch_number: prev_verified_batch_number,
+ recheck_interval: recheck_interval
+ } = state
+ ) do
+ {latest_batch_number, virtual_batch_number, verified_batch_number} =
+ fetch_latest_batch_numbers(json_rpc_named_arguments)
+
+ {new_state, handle_duration} =
+ if latest_batch_number > prev_latest_batch_number or virtual_batch_number > prev_virtual_batch_number or
+ verified_batch_number > prev_verified_batch_number do
+ start_batch_number = Reader.last_verified_batch_number() + 1
+ end_batch_number = latest_batch_number
+
+ log_message =
+ ""
+ |> make_log_message(latest_batch_number, prev_latest_batch_number, "latest")
+ |> make_log_message(virtual_batch_number, prev_virtual_batch_number, "virtual")
+ |> make_log_message(verified_batch_number, prev_verified_batch_number, "verified")
+
+ Logger.info(log_message <> "Handling the batch range #{start_batch_number}..#{end_batch_number}.")
+
+ {handle_duration, _} =
+ :timer.tc(fn ->
+ handle_batch_range(start_batch_number, end_batch_number, json_rpc_named_arguments, chunk_size)
+ end)
+
+ {
+ %{
+ state
+ | prev_latest_batch_number: latest_batch_number,
+ prev_virtual_batch_number: virtual_batch_number,
+ prev_verified_batch_number: verified_batch_number
+ },
+ div(handle_duration, 1000)
+ }
+ else
+ {state, 0}
+ end
+
+ Process.send_after(self(), :continue, max(:timer.seconds(recheck_interval) - handle_duration, 0))
+
+ {:noreply, new_state}
+ end
+
+ @impl GenServer
+ def handle_info({ref, _result}, state) do
+ Process.demonitor(ref, [:flush])
+ {:noreply, state}
+ end
+
+ defp handle_batch_range(start_batch_number, end_batch_number, json_rpc_named_arguments, chunk_size) do
+ start_batch_number..end_batch_number
+ |> Enum.chunk_every(chunk_size)
+ |> Enum.each(fn chunk ->
+ chunk_start = List.first(chunk)
+ chunk_end = List.last(chunk)
+
+ log_batches_chunk_handling(chunk_start, chunk_end, start_batch_number, end_batch_number)
+ fetch_and_save_batches(chunk_start, chunk_end, json_rpc_named_arguments)
+ end)
+ end
+
+ defp log_batches_chunk_handling(chunk_start, chunk_end, start_block, end_block) do
+ target_range =
+ if chunk_start != start_block or chunk_end != end_block do
+ percentage =
+ (chunk_end - start_block + 1)
+ |> Decimal.div(end_block - start_block + 1)
+ |> Decimal.mult(100)
+ |> Decimal.round(2)
+ |> Decimal.to_string()
+
+ " Target range: #{start_block}..#{end_block}. Progress: #{percentage}%"
+ else
+ ""
+ end
+
+ if chunk_start == chunk_end do
+ Logger.info("Handling batch ##{chunk_start}.#{target_range}")
+ else
+ Logger.info("Handling batch range #{chunk_start}..#{chunk_end}.#{target_range}")
+ end
+ end
+
+ defp make_log_message(prev_message, batch_number, prev_batch_number, type) do
+ if batch_number > prev_batch_number do
+ prev_message <>
+ "Found a new #{type} batch number #{batch_number}. Previous #{type} batch number is #{prev_batch_number}. "
+ else
+ prev_message
+ end
+ end
+
+ defp fetch_and_save_batches(batch_start, batch_end, json_rpc_named_arguments) do
+ requests =
+ batch_start
+ |> Range.new(batch_end, 1)
+ |> Enum.map(fn batch_number ->
+ EthereumJSONRPC.request(%{
+ id: batch_number,
+ method: "zkevm_getBatchByNumber",
+ params: [integer_to_quantity(batch_number), false]
+ })
+ end)
+
+ error_message =
+ &"Cannot call zkevm_getBatchByNumber for the batch range #{batch_start}..#{batch_end}. Error: #{inspect(&1)}"
+
+ {:ok, responses} = repeated_call(&json_rpc/2, [requests, json_rpc_named_arguments], error_message, 3)
+
+ {sequence_hashes, verify_hashes} =
+ responses
+ |> Enum.reduce({[], []}, fn res, {sequences, verifies} = _acc ->
+ send_sequences_tx_hash = get_tx_hash(res.result, "sendSequencesTxHash")
+ verify_batch_tx_hash = get_tx_hash(res.result, "verifyBatchTxHash")
+
+ sequences =
+ if send_sequences_tx_hash != @zero_hash do
+ [Base.decode16!(send_sequences_tx_hash, case: :mixed) | sequences]
+ else
+ sequences
+ end
+
+ verifies =
+ if verify_batch_tx_hash != @zero_hash do
+ [Base.decode16!(verify_batch_tx_hash, case: :mixed) | verifies]
+ else
+ verifies
+ end
+
+ {sequences, verifies}
+ end)
+
+ l1_tx_hashes = Enum.uniq(sequence_hashes ++ verify_hashes)
+
+ hash_to_id =
+ l1_tx_hashes
+ |> Reader.lifecycle_transactions()
+ |> Enum.reduce(%{}, fn {hash, id}, acc ->
+ Map.put(acc, hash.bytes, id)
+ end)
+
+ {batches_to_import, l2_txs_to_import, l1_txs_to_import, _, _} =
+ responses
+ |> Enum.reduce({[], [], [], Reader.next_id(), hash_to_id}, fn res,
+ {batches, l2_txs, l1_txs, next_id, hash_to_id} =
+ _acc ->
+ number = quantity_to_integer(Map.get(res.result, "number"))
+ {:ok, timestamp} = DateTime.from_unix(quantity_to_integer(Map.get(res.result, "timestamp")))
+ l2_transaction_hashes = Map.get(res.result, "transactions")
+ global_exit_root = Map.get(res.result, "globalExitRoot")
+ acc_input_hash = Map.get(res.result, "accInputHash")
+ state_root = Map.get(res.result, "stateRoot")
+
+ {sequence_id, l1_txs, next_id, hash_to_id} =
+ res.result
+ |> get_tx_hash("sendSequencesTxHash")
+ |> handle_tx_hash(hash_to_id, next_id, l1_txs, false)
+
+ {verify_id, l1_txs, next_id, hash_to_id} =
+ res.result
+ |> get_tx_hash("verifyBatchTxHash")
+ |> handle_tx_hash(hash_to_id, next_id, l1_txs, true)
+
+ l2_txs_append =
+ l2_transaction_hashes
+ |> Kernel.||([])
+ |> Enum.map(fn l2_tx_hash ->
+ %{
+ batch_number: number,
+ hash: l2_tx_hash
+ }
+ end)
+
+ batch = %{
+ number: number,
+ timestamp: timestamp,
+ l2_transactions_count: Enum.count(l2_txs_append),
+ global_exit_root: global_exit_root,
+ acc_input_hash: acc_input_hash,
+ state_root: state_root,
+ sequence_id: sequence_id,
+ verify_id: verify_id
+ }
+
+ {[batch | batches], l2_txs ++ l2_txs_append, l1_txs, next_id, hash_to_id}
+ end)
+
+ {:ok, _} =
+ Chain.import(%{
+ zkevm_lifecycle_transactions: %{params: l1_txs_to_import},
+ zkevm_transaction_batches: %{params: batches_to_import},
+ zkevm_batch_transactions: %{params: l2_txs_to_import},
+ timeout: :infinity
+ })
+
+ confirmed_batches =
+ Enum.filter(batches_to_import, fn batch -> not is_nil(batch.sequence_id) and batch.sequence_id > 0 end)
+
+ if not Enum.empty?(confirmed_batches) do
+ Publisher.broadcast([{:zkevm_confirmed_batches, confirmed_batches}], :realtime)
+ end
+ end
+
+ defp fetch_latest_batch_numbers(json_rpc_named_arguments) do
+ requests = [
+ EthereumJSONRPC.request(%{id: 0, method: "zkevm_batchNumber", params: []}),
+ EthereumJSONRPC.request(%{id: 1, method: "zkevm_virtualBatchNumber", params: []}),
+ EthereumJSONRPC.request(%{id: 2, method: "zkevm_verifiedBatchNumber", params: []})
+ ]
+
+ error_message = &"Cannot call zkevm_batchNumber. Error: #{inspect(&1)}"
+
+ {:ok, responses} = repeated_call(&json_rpc/2, [requests, json_rpc_named_arguments], error_message, 3)
+
+ latest_batch_number =
+ Enum.find_value(responses, fn resp -> if resp.id == 0, do: quantity_to_integer(resp.result) end)
+
+ virtual_batch_number =
+ Enum.find_value(responses, fn resp -> if resp.id == 1, do: quantity_to_integer(resp.result) end)
+
+ verified_batch_number =
+ Enum.find_value(responses, fn resp -> if resp.id == 2, do: quantity_to_integer(resp.result) end)
+
+ {latest_batch_number, virtual_batch_number, verified_batch_number}
+ end
+
+ defp get_tx_hash(result, type) do
+ case Map.get(result, type) do
+ "0x" <> tx_hash -> tx_hash
+ nil -> @zero_hash
+ end
+ end
+
+ defp handle_tx_hash(encoded_tx_hash, hash_to_id, next_id, l1_txs, is_verify) do
+ if encoded_tx_hash != @zero_hash do
+ tx_hash = Base.decode16!(encoded_tx_hash, case: :mixed)
+
+ id = Map.get(hash_to_id, tx_hash)
+
+ if is_nil(id) do
+ {next_id, [%{id: next_id, hash: tx_hash, is_verify: is_verify} | l1_txs], next_id + 1,
+ Map.put(hash_to_id, tx_hash, next_id)}
+ else
+ {id, l1_txs, next_id, hash_to_id}
+ end
+ else
+ {nil, l1_txs, next_id, hash_to_id}
+ end
+ end
+
+ defp repeated_call(func, args, error_message, retries_left) do
+ case apply(func, args) do
+ {:ok, _} = res ->
+ res
+
+ {:error, message} = err ->
+ retries_left = retries_left - 1
+
+ if retries_left <= 0 do
+ Logger.error(error_message.(message))
+ err
+ else
+ Logger.error("#{error_message.(message)} Retrying...")
+ :timer.sleep(3000)
+ repeated_call(func, args, error_message, retries_left)
+ end
+ end
+ end
+end
diff --git a/apps/indexer/lib/indexer/helper.ex b/apps/indexer/lib/indexer/helper.ex
new file mode 100644
index 000000000000..21b69831f26e
--- /dev/null
+++ b/apps/indexer/lib/indexer/helper.ex
@@ -0,0 +1,44 @@
+defmodule Indexer.Helper do
+ @moduledoc """
+ Auxiliary common functions for indexers.
+ """
+
+ alias Explorer.Chain.Hash
+
+ @spec address_hash_to_string(binary(), boolean()) :: binary()
+ def address_hash_to_string(hash, downcase \\ false)
+
+ def address_hash_to_string(hash, downcase) when is_binary(hash) do
+ if downcase do
+ String.downcase(hash)
+ else
+ hash
+ end
+ end
+
+ def address_hash_to_string(hash, downcase) do
+ if downcase do
+ String.downcase(Hash.to_string(hash))
+ else
+ Hash.to_string(hash)
+ end
+ end
+
+ @spec is_address_correct?(binary()) :: boolean()
+ def is_address_correct?(address) when is_binary(address) do
+ String.match?(address, ~r/^0x[[:xdigit:]]{40}$/i)
+ end
+
+ def is_address_correct?(_address) do
+ false
+ end
+
+ @spec log_topic_to_string(any()) :: binary() | nil
+ def log_topic_to_string(topic) do
+ if is_binary(topic) or is_nil(topic) do
+ topic
+ else
+ Hash.to_string(topic)
+ end
+ end
+end
diff --git a/apps/indexer/lib/indexer/memory/monitor.ex b/apps/indexer/lib/indexer/memory/monitor.ex
index 40562a0f1330..704592f2d645 100644
--- a/apps/indexer/lib/indexer/memory/monitor.ex
+++ b/apps/indexer/lib/indexer/memory/monitor.ex
@@ -14,7 +14,7 @@ defmodule Indexer.Memory.Monitor do
alias Indexer.Memory.Shrinkable
- defstruct limit: Application.get_env(:indexer, :memory_limit),
+ defstruct limit: 0,
timer_interval: :timer.minutes(1),
timer_reference: nil,
shrinkable_set: MapSet.new()
@@ -62,11 +62,11 @@ defmodule Indexer.Memory.Monitor do
end
@impl GenServer
- def handle_info(:check, %__MODULE__{limit: limit} = state) do
+ def handle_info(:check, state) do
total = :erlang.memory(:total)
- if limit < total do
- log_memory(%{limit: limit, total: total})
+ if memory_limit() < total do
+ log_memory(%{limit: memory_limit(), total: total})
shrink_or_log(state)
end
@@ -169,4 +169,8 @@ defmodule Indexer.Memory.Monitor do
|> Enum.map(fn pid -> {pid, memory(pid)} end)
|> Enum.sort_by(&elem(&1, 1), &>=/2)
end
+
+ defp memory_limit do
+ Application.get_env(:indexer, :memory_limit)
+ end
end
diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex
index 38b2e39f6d25..10a745512bd0 100644
--- a/apps/indexer/lib/indexer/supervisor.ex
+++ b/apps/indexer/lib/indexer/supervisor.ex
@@ -13,6 +13,7 @@ defmodule Indexer.Supervisor do
alias Indexer.Block.Catchup, as: BlockCatchup
alias Indexer.Block.Realtime, as: BlockRealtime
+ alias Indexer.Fetcher.TokenInstance.LegacySanitize, as: TokenInstanceLegacySanitize
alias Indexer.Fetcher.TokenInstance.Realtime, as: TokenInstanceRealtime
alias Indexer.Fetcher.TokenInstance.Retry, as: TokenInstanceRetry
alias Indexer.Fetcher.TokenInstance.Sanitize, as: TokenInstanceSanitize
@@ -20,20 +21,26 @@ defmodule Indexer.Supervisor do
alias Indexer.Fetcher.{
BlockReward,
CoinBalance,
+ CoinBalanceDailyUpdater,
ContractCode,
EmptyBlocksSanitizer,
InternalTransaction,
PendingBlockOperationsSanitizer,
PendingTransaction,
+ PolygonEdge,
ReplacedTransaction,
+ RootstockData,
Token,
TokenBalance,
+ TokenTotalSupplyUpdater,
TokenUpdater,
TransactionAction,
UncleBlock,
Withdrawal
}
+ alias Indexer.Fetcher.Zkevm.TransactionBatch
+
alias Indexer.Temporary.{
BlocksTransactionsMismatch,
UncatalogedTokenTransfers,
@@ -113,6 +120,7 @@ defmodule Indexer.Supervisor do
{TokenInstanceRealtime.Supervisor, [[memory_monitor: memory_monitor]]},
{TokenInstanceRetry.Supervisor, [[memory_monitor: memory_monitor]]},
{TokenInstanceSanitize.Supervisor, [[memory_monitor: memory_monitor]]},
+ {TokenInstanceLegacySanitize, [[memory_monitor: memory_monitor]]},
configure(TransactionAction.Supervisor, [[memory_monitor: memory_monitor]]),
{ContractCode.Supervisor,
[[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]},
@@ -121,10 +129,22 @@ defmodule Indexer.Supervisor do
{TokenUpdater.Supervisor,
[[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]},
{ReplacedTransaction.Supervisor, [[memory_monitor: memory_monitor]]},
+ {PolygonEdge.Supervisor, [[memory_monitor: memory_monitor]]},
+ {Indexer.Fetcher.PolygonEdge.Deposit.Supervisor, [[memory_monitor: memory_monitor]]},
+ {Indexer.Fetcher.PolygonEdge.DepositExecute.Supervisor,
+ [[memory_monitor: memory_monitor, json_rpc_named_arguments: json_rpc_named_arguments]]},
+ {Indexer.Fetcher.PolygonEdge.Withdrawal.Supervisor,
+ [[memory_monitor: memory_monitor, json_rpc_named_arguments: json_rpc_named_arguments]]},
+ {Indexer.Fetcher.PolygonEdge.WithdrawalExit.Supervisor, [[memory_monitor: memory_monitor]]},
+ configure(TransactionBatch.Supervisor, [
+ [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]
+ ]),
# Out-of-band fetchers
{EmptyBlocksSanitizer.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments]]},
{PendingTransactionsSanitizer, [[json_rpc_named_arguments: json_rpc_named_arguments]]},
+ {TokenTotalSupplyUpdater, [[]]},
+ {CoinBalanceDailyUpdater, [[]]},
# Temporary workers
{UncatalogedTokenTransfers.Supervisor, [[]]},
@@ -134,17 +154,20 @@ defmodule Indexer.Supervisor do
[[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]},
{PendingOpsCleaner, [[], []]},
{PendingBlockOperationsSanitizer, [[]]},
+ {RootstockData.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments]]},
# Block fetchers
configure(BlockRealtime.Supervisor, [
%{block_fetcher: realtime_block_fetcher, subscribe_named_arguments: realtime_subscribe_named_arguments},
[name: BlockRealtime.Supervisor]
]),
- {BlockCatchup.Supervisor,
- [
- %{block_fetcher: block_fetcher, block_interval: block_interval, memory_monitor: memory_monitor},
- [name: BlockCatchup.Supervisor]
- ]},
+ configure(
+ BlockCatchup.Supervisor,
+ [
+ %{block_fetcher: block_fetcher, block_interval: block_interval, memory_monitor: memory_monitor},
+ [name: BlockCatchup.Supervisor]
+ ]
+ ),
{Withdrawal.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments]]}
]
|> List.flatten()
diff --git a/apps/indexer/lib/indexer/temporary/blocks_transactions_mismatch.ex b/apps/indexer/lib/indexer/temporary/blocks_transactions_mismatch.ex
index 1be13f7776a4..fd7e1907313c 100644
--- a/apps/indexer/lib/indexer/temporary/blocks_transactions_mismatch.ex
+++ b/apps/indexer/lib/indexer/temporary/blocks_transactions_mismatch.ex
@@ -14,8 +14,8 @@ defmodule Indexer.Temporary.BlocksTransactionsMismatch do
import Ecto.Query
alias EthereumJSONRPC.Blocks
+ alias Explorer.{Chain, Repo}
alias Explorer.Chain.Block
- alias Explorer.Repo
alias Explorer.Utility.MissingRangesManipulator
alias Indexer.BufferedTask
@@ -58,7 +58,10 @@ defmodule Indexer.Temporary.BlocksTransactionsMismatch do
select: {block.hash, count(transactions.hash)}
)
- {:ok, final} = Repo.stream_reduce(query, initial, &reducer.(&1, &2))
+ {:ok, final} =
+ query
+ |> Chain.add_fetcher_limit(true)
+ |> Repo.stream_reduce(initial, &reducer.(&1, &2))
final
end
@@ -133,7 +136,7 @@ defmodule Indexer.Temporary.BlocksTransactionsMismatch do
where: block.hash in ^hashes,
# Enforce Block ShareLocks order (see docs: sharelocks.md)
order_by: [asc: block.hash],
- lock: "FOR UPDATE"
+ lock: "FOR NO KEY UPDATE"
)
Repo.update_all(
diff --git a/apps/indexer/lib/indexer/temporary/uncataloged_token_transfers.ex b/apps/indexer/lib/indexer/temporary/uncataloged_token_transfers.ex
index 17f676f136d5..d048a3331172 100644
--- a/apps/indexer/lib/indexer/temporary/uncataloged_token_transfers.ex
+++ b/apps/indexer/lib/indexer/temporary/uncataloged_token_transfers.ex
@@ -12,7 +12,7 @@ defmodule Indexer.Temporary.UncatalogedTokenTransfers do
require Logger
- alias Explorer.Chain
+ alias Explorer.Chain.TokenTransfer
alias Indexer.Block.Catchup.Fetcher
alias Indexer.Temporary.UncatalogedTokenTransfers
@@ -52,7 +52,7 @@ defmodule Indexer.Temporary.UncatalogedTokenTransfers do
end
def handle_info(:scan, state) do
- {:ok, block_numbers} = Chain.uncataloged_token_transfer_block_numbers()
+ {:ok, block_numbers} = TokenTransfer.uncataloged_token_transfer_block_numbers()
case block_numbers do
[] ->
diff --git a/apps/indexer/lib/indexer/temporary/uncles_without_index.ex b/apps/indexer/lib/indexer/temporary/uncles_without_index.ex
index 238778e5b91e..79abdc18147a 100644
--- a/apps/indexer/lib/indexer/temporary/uncles_without_index.ex
+++ b/apps/indexer/lib/indexer/temporary/uncles_without_index.ex
@@ -52,13 +52,15 @@ defmodule Indexer.Temporary.UnclesWithoutIndex do
query =
from(bsdr in SecondDegreeRelation,
join: block in assoc(bsdr, :nephew),
- where: is_nil(bsdr.index) and is_nil(bsdr.uncle_fetched_at) and block.consensus,
+ where: is_nil(bsdr.index) and is_nil(bsdr.uncle_fetched_at) and block.consensus == true,
select: bsdr.nephew_hash,
group_by: bsdr.nephew_hash
)
{:ok, final} =
- Repo.stream_reduce(query, initial, fn nephew_hash, acc ->
+ query
+ |> Chain.add_fetcher_limit(true)
+ |> Repo.stream_reduce(initial, fn nephew_hash, acc ->
nephew_hash
|> to_string()
|> reducer.(acc)
diff --git a/apps/indexer/lib/indexer/transform/address_coin_balances.ex b/apps/indexer/lib/indexer/transform/address_coin_balances.ex
index 03319b0dec35..4c7a20ca66ea 100644
--- a/apps/indexer/lib/indexer/transform/address_coin_balances.ex
+++ b/apps/indexer/lib/indexer/transform/address_coin_balances.ex
@@ -3,6 +3,8 @@ defmodule Indexer.Transform.AddressCoinBalances do
Extracts `Explorer.Chain.Address.CoinBalance` params from other schema's params.
"""
+ alias Explorer.Chain.TokenTransfer
+
def params_set(%{} = import_options) do
Enum.reduce(import_options, MapSet.new(), &reducer/2)
end
@@ -30,6 +32,7 @@ defmodule Indexer.Transform.AddressCoinBalances do
defp reducer({:logs_params, logs_params}, acc) when is_list(logs_params) do
# a log MUST have address_hash and block_number
logs_params
+ |> Enum.reject(&(&1.first_topic == TokenTransfer.constant()))
|> Enum.into(acc, fn
%{address_hash: address_hash, block_number: block_number}
when is_binary(address_hash) and is_integer(block_number) ->
diff --git a/apps/indexer/lib/indexer/transform/address_token_balances.ex b/apps/indexer/lib/indexer/transform/address_token_balances.ex
index 3dc7b5be525d..291783f6a2ba 100644
--- a/apps/indexer/lib/indexer/transform/address_token_balances.ex
+++ b/apps/indexer/lib/indexer/transform/address_token_balances.ex
@@ -3,7 +3,7 @@ defmodule Indexer.Transform.AddressTokenBalances do
Extracts `Explorer.Address.TokenBalance` params from other schema's params.
"""
- @burn_address "0x0000000000000000000000000000000000000000"
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
def params_set(%{} = import_options) do
Enum.reduce(import_options, MapSet.new(), &reducer/2)
@@ -11,7 +11,6 @@ defmodule Indexer.Transform.AddressTokenBalances do
defp reducer({:token_transfers_params, token_transfers_params}, initial) when is_list(token_transfers_params) do
token_transfers_params
- |> ignore_burn_address_transfers_for_token_erc_721()
|> Enum.reduce(initial, fn %{
block_number: block_number,
from_address_hash: from_address_hash,
@@ -31,11 +30,7 @@ defmodule Indexer.Transform.AddressTokenBalances do
end)
end
- defp ignore_burn_address_transfers_for_token_erc_721(token_transfers_params) do
- Enum.filter(token_transfers_params, &do_filter_burn_address/1)
- end
-
- defp add_token_balance_address(map_set, unquote(@burn_address), _, _, _, _), do: map_set
+ defp add_token_balance_address(map_set, unquote(burn_address_hash_string()), _, _, _, _), do: map_set
defp add_token_balance_address(map_set, address, token_contract_address, token_id, token_type, block_number) do
MapSet.put(map_set, %{
@@ -46,12 +41,4 @@ defmodule Indexer.Transform.AddressTokenBalances do
token_type: token_type
})
end
-
- def do_filter_burn_address(%{to_address_hash: unquote(@burn_address), token_type: "ERC-721"}) do
- false
- end
-
- def do_filter_burn_address(_token_balance_param) do
- true
- end
end
diff --git a/apps/indexer/lib/indexer/transform/addresses.ex b/apps/indexer/lib/indexer/transform/addresses.ex
index 5787cc62bb06..5e3a0e227999 100644
--- a/apps/indexer/lib/indexer/transform/addresses.ex
+++ b/apps/indexer/lib/indexer/transform/addresses.ex
@@ -96,6 +96,10 @@ defmodule Indexer.Transform.Addresses do
[
%{from: :block_number, to: :fetched_coin_balance_block_number},
%{from: :to_address_hash, to: :hash}
+ ],
+ [
+ %{from: :execution_node_hash, to: :hash},
+ %{from: :wrapped_to_address_hash, to: :hash}
]
],
logs: [
@@ -399,7 +403,9 @@ defmodule Indexer.Transform.Addresses do
required(:from_address_hash) => String.t(),
required(:nonce) => non_neg_integer(),
optional(:to_address_hash) => String.t(),
- optional(:created_contract_address_hash) => String.t()
+ optional(:created_contract_address_hash) => String.t(),
+ optional(:execution_node_hash) => String.t(),
+ optional(:wrapped_to_address_hash) => String.t()
}
],
optional(:logs) => [
diff --git a/apps/indexer/lib/indexer/transform/blocks.ex b/apps/indexer/lib/indexer/transform/blocks.ex
index 37a748795210..195e3642765e 100644
--- a/apps/indexer/lib/indexer/transform/blocks.ex
+++ b/apps/indexer/lib/indexer/transform/blocks.ex
@@ -3,6 +3,8 @@ defmodule Indexer.Transform.Blocks do
Protocol for transforming blocks.
"""
+ alias ExSecp256k1
+
@type block :: map()
@doc """
@@ -91,8 +93,7 @@ defmodule Indexer.Transform.Blocks do
# First byte represents compression which can be ignored
# Private key is the last 64 bytes
- {:ok, <<_compression::bytes-size(1), private_key::binary>>} =
- :libsecp256k1.ecdsa_recover_compact(signature_hash, r <> s, :uncompressed, v)
+ {:ok, <<_compression::bytes-size(1), private_key::binary>>} = ExSecp256k1.recover(signature_hash, r, s, v)
# Public key comes from the last 20 bytes
<<_::bytes-size(12), public_key::binary>> = ExKeccak.hash_256(private_key)
diff --git a/apps/indexer/lib/indexer/transform/mint_transfers.ex b/apps/indexer/lib/indexer/transform/mint_transfers.ex
index e57a9841a7fb..c53dde7a5387 100644
--- a/apps/indexer/lib/indexer/transform/mint_transfers.ex
+++ b/apps/indexer/lib/indexer/transform/mint_transfers.ex
@@ -24,8 +24,7 @@ defmodule Indexer.Transform.MintTransfers do
...> index: 1,
...> second_topic: "0x0000000000000000000000009a4a90e2732f3fa4087b0bb4bf85c76d14833df1",
...> third_topic: "0x0000000000000000000000007301cfa0e1756b71869e93d4e4dca5c7d0eb0aa6",
- ...> transaction_hash: "0x1d5066d30ff3404a9306733136103ac2b0b989951c38df637f464f3667f8d4ee",
- ...> type: "mined"
+ ...> transaction_hash: "0x1d5066d30ff3404a9306733136103ac2b0b989951c38df637f464f3667f8d4ee"
...> }
...> ])
%{
diff --git a/apps/indexer/lib/indexer/transform/polygon_edge/deposit_executes.ex b/apps/indexer/lib/indexer/transform/polygon_edge/deposit_executes.ex
new file mode 100644
index 000000000000..93dec160353c
--- /dev/null
+++ b/apps/indexer/lib/indexer/transform/polygon_edge/deposit_executes.ex
@@ -0,0 +1,55 @@
+defmodule Indexer.Transform.PolygonEdge.DepositExecutes do
+ @moduledoc """
+ Helper functions for transforming data for Polygon Edge deposit executes.
+ """
+
+ require Logger
+
+ alias Indexer.Fetcher.PolygonEdge.DepositExecute
+ alias Indexer.Helper
+
+ @doc """
+ Returns a list of deposit executes given a list of logs.
+ """
+ @spec parse(list()) :: list()
+ def parse(logs) do
+ prev_metadata = Logger.metadata()
+ Logger.metadata(fetcher: :polygon_edge_deposit_executes_realtime)
+
+ items =
+ with false <-
+ is_nil(Application.get_env(:indexer, DepositExecute)[:start_block_l2]),
+ state_receiver = Application.get_env(:indexer, DepositExecute)[:state_receiver],
+ true <- Helper.is_address_correct?(state_receiver) do
+ state_receiver = String.downcase(state_receiver)
+ state_sync_result_event_signature = DepositExecute.state_sync_result_event_signature()
+
+ logs
+ |> Enum.filter(fn log ->
+ !is_nil(log.first_topic) && String.downcase(log.first_topic) == state_sync_result_event_signature &&
+ String.downcase(Helper.address_hash_to_string(log.address_hash)) == state_receiver
+ end)
+ |> Enum.map(fn log ->
+ Logger.info("Deposit Execute (StateSyncResult) message found, id: #{log.second_topic}.")
+
+ DepositExecute.event_to_deposit_execute(
+ log.second_topic,
+ log.third_topic,
+ log.transaction_hash,
+ log.block_number
+ )
+ end)
+ else
+ true ->
+ []
+
+ false ->
+ Logger.error("StateReceiver contract address is incorrect. Cannot use #{__MODULE__} for parsing logs.")
+ []
+ end
+
+ Logger.reset_metadata(prev_metadata)
+
+ items
+ end
+end
diff --git a/apps/indexer/lib/indexer/transform/polygon_edge/withdrawals.ex b/apps/indexer/lib/indexer/transform/polygon_edge/withdrawals.ex
new file mode 100644
index 000000000000..1562f1fea61d
--- /dev/null
+++ b/apps/indexer/lib/indexer/transform/polygon_edge/withdrawals.ex
@@ -0,0 +1,54 @@
+defmodule Indexer.Transform.PolygonEdge.Withdrawals do
+ @moduledoc """
+ Helper functions for transforming data for Polygon Edge withdrawals.
+ """
+
+ require Logger
+
+ alias Indexer.Fetcher.PolygonEdge.Withdrawal
+ alias Indexer.Helper
+
+ @doc """
+ Returns a list of withdrawals given a list of logs.
+ """
+ @spec parse(list()) :: list()
+ def parse(logs) do
+ prev_metadata = Logger.metadata()
+ Logger.metadata(fetcher: :polygon_edge_withdrawals_realtime)
+
+ items =
+ with false <- is_nil(Application.get_env(:indexer, Withdrawal)[:start_block_l2]),
+ state_sender = Application.get_env(:indexer, Withdrawal)[:state_sender],
+ true <- Helper.is_address_correct?(state_sender) do
+ state_sender = String.downcase(state_sender)
+ l2_state_synced_event_signature = Withdrawal.l2_state_synced_event_signature()
+
+ logs
+ |> Enum.filter(fn log ->
+ !is_nil(log.first_topic) && String.downcase(log.first_topic) == l2_state_synced_event_signature &&
+ String.downcase(Helper.address_hash_to_string(log.address_hash)) == state_sender
+ end)
+ |> Enum.map(fn log ->
+ Logger.info("Withdrawal message found, id: #{log.second_topic}.")
+
+ Withdrawal.event_to_withdrawal(
+ log.second_topic,
+ log.data,
+ log.transaction_hash,
+ log.block_number
+ )
+ end)
+ else
+ true ->
+ []
+
+ false ->
+ Logger.error("L2StateSender contract address is incorrect. Cannot use #{__MODULE__} for parsing logs.")
+ []
+ end
+
+ Logger.reset_metadata(prev_metadata)
+
+ items
+ end
+end
diff --git a/apps/indexer/lib/indexer/transform/token_instances.ex b/apps/indexer/lib/indexer/transform/token_instances.ex
new file mode 100644
index 000000000000..a9fb4372d2fd
--- /dev/null
+++ b/apps/indexer/lib/indexer/transform/token_instances.ex
@@ -0,0 +1,88 @@
+defmodule Indexer.Transform.TokenInstances do
+ @moduledoc """
+ Module extracts token instances from token transfers
+ """
+
+ def params_set(%{} = import_options) do
+ Enum.reduce(import_options, %{}, &reducer/2)
+ end
+
+ defp reducer({:token_transfers_params, token_transfers_params}, initial) when is_list(token_transfers_params) do
+ token_transfers_params
+ |> Enum.reduce(initial, fn
+ %{
+ block_number: block_number,
+ from_address_hash: from_address_hash,
+ to_address_hash: to_address_hash,
+ token_contract_address_hash: token_contract_address_hash,
+ token_ids: [_ | _]
+ } = tt,
+ acc
+ when is_integer(block_number) and
+ is_binary(from_address_hash) and
+ is_binary(to_address_hash) and is_binary(token_contract_address_hash) ->
+ transfer_to_instances(tt, acc)
+
+ _, acc ->
+ acc
+ end)
+ |> Map.values()
+ end
+
+ defp transfer_to_instances(
+ %{
+ token_type: "ERC-721" = token_type,
+ to_address_hash: to_address_hash,
+ token_ids: [token_id],
+ token_contract_address_hash: token_contract_address_hash,
+ block_number: block_number,
+ log_index: log_index
+ },
+ acc
+ ) do
+ params = %{
+ token_contract_address_hash: token_contract_address_hash,
+ token_id: token_id,
+ token_type: token_type,
+ owner_address_hash: to_address_hash,
+ owner_updated_at_block: block_number,
+ owner_updated_at_log_index: log_index
+ }
+
+ current_key = {token_contract_address_hash, token_id}
+
+ Map.put(
+ acc,
+ current_key,
+ Enum.max_by(
+ [
+ params,
+ acc[current_key] || params
+ ],
+ fn %{
+ owner_updated_at_block: owner_updated_at_block,
+ owner_updated_at_log_index: owner_updated_at_log_index
+ } ->
+ {owner_updated_at_block, owner_updated_at_log_index}
+ end
+ )
+ )
+ end
+
+ defp transfer_to_instances(
+ %{
+ token_type: _token_type,
+ token_ids: [_ | _] = token_ids,
+ token_contract_address_hash: token_contract_address_hash
+ },
+ acc
+ ) do
+ Enum.reduce(token_ids, acc, fn id, sub_acc ->
+ Map.put(sub_acc, {token_contract_address_hash, id}, %{
+ token_contract_address_hash: token_contract_address_hash,
+ token_id: id,
+ token_type: "ERC-1155"
+ })
+ end)
+ end
+end
diff --git a/apps/indexer/lib/indexer/transform/token_transfers.ex b/apps/indexer/lib/indexer/transform/token_transfers.ex
index 27e4291b0648..7b10e701b9a4 100644
--- a/apps/indexer/lib/indexer/transform/token_transfers.ex
+++ b/apps/indexer/lib/indexer/transform/token_transfers.ex
@@ -5,12 +5,12 @@ defmodule Indexer.Transform.TokenTransfers do
require Logger
- alias ABI.TypeDecoder
- alias Explorer.{Chain, Repo}
- alias Explorer.Chain.{Token, TokenTransfer}
- alias Explorer.Token.MetadataRetriever
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
+ import Explorer.Helper, only: [decode_data: 2]
- @burn_address "0x0000000000000000000000000000000000000000"
+ alias Explorer.Repo
+ alias Explorer.Chain.{Token, TokenTransfer}
+ alias Indexer.Fetcher.TokenTotalSupplyUpdater
@doc """
Returns a list of token transfers given a list of logs.
@@ -23,6 +23,14 @@ defmodule Indexer.Transform.TokenTransfers do
|> Enum.filter(&(&1.first_topic == unquote(TokenTransfer.constant())))
|> Enum.reduce(initial_acc, &do_parse/2)
+ weth_transfers =
+ logs
+ |> Enum.filter(fn log ->
+ log.first_topic == TokenTransfer.weth_deposit_signature() ||
+ log.first_topic == TokenTransfer.weth_withdrawal_signature()
+ end)
+ |> Enum.reduce(initial_acc, &do_parse/2)
+
erc1155_token_transfers =
logs
|> Enum.filter(fn log ->
@@ -31,20 +39,26 @@ defmodule Indexer.Transform.TokenTransfers do
end)
|> Enum.reduce(initial_acc, &do_parse(&1, &2, :erc1155))
- rough_tokens = erc1155_token_transfers.tokens ++ erc20_and_erc721_token_transfers.tokens
- rough_token_transfers = erc1155_token_transfers.token_transfers ++ erc20_and_erc721_token_transfers.token_transfers
+ rough_tokens =
+ erc1155_token_transfers.tokens ++
+ erc20_and_erc721_token_transfers.tokens ++ weth_transfers.tokens
+
+ rough_token_transfers =
+ erc1155_token_transfers.token_transfers ++
+ erc20_and_erc721_token_transfers.token_transfers ++ weth_transfers.token_transfers
{tokens, token_transfers} = sanitize_token_types(rough_tokens, rough_token_transfers)
token_transfers
|> Enum.filter(fn token_transfer ->
- token_transfer.to_address_hash == @burn_address || token_transfer.from_address_hash == @burn_address
+ token_transfer.to_address_hash == burn_address_hash_string() ||
+ token_transfer.from_address_hash == burn_address_hash_string()
end)
|> Enum.map(fn token_transfer ->
token_transfer.token_contract_address_hash
end)
|> Enum.uniq()
- |> Enum.each(&update_token/1)
+ |> TokenTotalSupplyUpdater.add_tokens()
tokens_uniq = tokens |> Enum.uniq()
@@ -67,22 +81,18 @@ defmodule Indexer.Transform.TokenTransfers do
end)
|> Map.new()
- existing_tokens =
- existing_token_types_map
- |> Map.keys()
- |> Enum.map(&to_string/1)
-
- new_tokens_token_transfers = Enum.filter(token_transfers, &(&1.token_contract_address_hash not in existing_tokens))
-
- new_token_types_map =
- new_tokens_token_transfers
+ token_types_map =
+ token_transfers
|> Enum.group_by(& &1.token_contract_address_hash)
|> Enum.map(fn {contract_address_hash, transfers} ->
{contract_address_hash, define_token_type(transfers)}
end)
|> Map.new()
- actual_token_types_map = Map.merge(new_token_types_map, existing_token_types_map)
+ actual_token_types_map =
+ Map.merge(token_types_map, existing_token_types_map, fn _k, new_type, old_type ->
+ if token_type_priority(old_type) > token_type_priority(new_type), do: old_type, else: new_type
+ end)
actual_tokens =
Enum.map(tokens, fn %{contract_address_hash: hash} = token ->
@@ -111,17 +121,23 @@ defmodule Indexer.Transform.TokenTransfers do
end
defp do_parse(log, %{tokens: tokens, token_transfers: token_transfers} = acc, type \\ :erc20_erc721) do
- {token, token_transfer} =
+ parse_result =
if type != :erc1155 do
parse_params(log)
else
parse_erc1155_params(log)
end
- %{
- tokens: [token | tokens],
- token_transfers: [token_transfer | token_transfers]
- }
+ case parse_result do
+ {token, token_transfer} ->
+ %{
+ tokens: [token | tokens],
+ token_transfers: [token_transfer | token_transfers]
+ }
+
+ nil ->
+ acc
+ end
rescue
e in [FunctionClauseError, MatchError] ->
Logger.error(fn ->
@@ -157,6 +173,39 @@ defmodule Indexer.Transform.TokenTransfers do
{token, token_transfer}
end
+ # ERC-20 token transfer for WETH
+ defp parse_params(%{second_topic: second_topic, third_topic: nil, fourth_topic: nil} = log)
+ when not is_nil(second_topic) do
+ [amount] = decode_data(log.data, [{:uint, 256}])
+
+ {from_address_hash, to_address_hash} =
+ if log.first_topic == TokenTransfer.weth_deposit_signature() do
+ {burn_address_hash_string(), truncate_address_hash(log.second_topic)}
+ else
+ {truncate_address_hash(log.second_topic), burn_address_hash_string()}
+ end
+
+ token_transfer = %{
+ amount: Decimal.new(amount || 0),
+ block_number: log.block_number,
+ block_hash: log.block_hash,
+ log_index: log.index,
+ from_address_hash: from_address_hash,
+ to_address_hash: to_address_hash,
+ token_contract_address_hash: log.address_hash,
+ transaction_hash: log.transaction_hash,
+ token_ids: nil,
+ token_type: "ERC-20"
+ }
+
+ token = %{
+ contract_address_hash: log.address_hash,
+ type: "ERC-20"
+ }
+
+ {token, token_transfer}
+ end
+
# ERC-721 token transfer with topics as addresses
defp parse_params(%{second_topic: second_topic, third_topic: third_topic, fourth_topic: fourth_topic} = log)
when not is_nil(second_topic) and not is_nil(third_topic) and not is_nil(fourth_topic) do
@@ -214,30 +263,6 @@ defmodule Indexer.Transform.TokenTransfers do
{token, token_transfer}
end
- defp update_token(nil), do: :ok
-
- defp update_token(address_hash_string) do
- {:ok, address_hash} = Chain.string_to_address_hash(address_hash_string)
-
- token = Repo.get_by(Token, contract_address_hash: address_hash)
-
- if token && !token.skip_metadata do
- token_params =
- address_hash_string
- |> MetadataRetriever.get_total_supply_of()
-
- token_to_update =
- token
- |> Repo.preload([:contract_address])
-
- if token_params !== %{} do
- {:ok, _} = Chain.update_token(token_to_update, token_params)
- end
- end
-
- :ok
- end
-
def parse_erc1155_params(
%{
first_topic: unquote(TokenTransfer.erc1155_batch_transfer_signature()),
@@ -248,25 +273,29 @@ defmodule Indexer.Transform.TokenTransfers do
) do
[token_ids, values] = decode_data(data, [{:array, {:uint, 256}}, {:array, {:uint, 256}}])
- token_transfer = %{
- block_number: log.block_number,
- block_hash: log.block_hash,
- log_index: log.index,
- from_address_hash: truncate_address_hash(third_topic),
- to_address_hash: truncate_address_hash(fourth_topic),
- token_contract_address_hash: log.address_hash,
- transaction_hash: log.transaction_hash,
- token_type: "ERC-1155",
- token_ids: token_ids,
- amounts: values
- }
-
- token = %{
- contract_address_hash: log.address_hash,
- type: "ERC-1155"
- }
-
- {token, token_transfer}
+ if token_ids == [] || values == [] do
+ nil
+ else
+ token_transfer = %{
+ block_number: log.block_number,
+ block_hash: log.block_hash,
+ log_index: log.index,
+ from_address_hash: truncate_address_hash(third_topic),
+ to_address_hash: truncate_address_hash(fourth_topic),
+ token_contract_address_hash: log.address_hash,
+ transaction_hash: log.transaction_hash,
+ token_type: "ERC-1155",
+ token_ids: token_ids,
+ amounts: values
+ }
+
+ token = %{
+ contract_address_hash: log.address_hash,
+ type: "ERC-1155"
+ }
+
+ {token, token_transfer}
+ end
end
def parse_erc1155_params(%{third_topic: third_topic, fourth_topic: fourth_topic, data: data} = log) do
@@ -293,7 +322,7 @@ defmodule Indexer.Transform.TokenTransfers do
{token, token_transfer}
end
- defp truncate_address_hash(nil), do: "0x0000000000000000000000000000000000000000"
+ defp truncate_address_hash(nil), do: burn_address_hash_string()
defp truncate_address_hash("0x000000000000000000000000" <> truncated_hash) do
"0x#{truncated_hash}"
@@ -302,14 +331,4 @@ defmodule Indexer.Transform.TokenTransfers do
defp encode_address_hash(binary) do
"0x" <> Base.encode16(binary, case: :lower)
end
-
- defp decode_data("0x", types) do
- for _ <- types, do: nil
- end
-
- defp decode_data("0x" <> encoded_data, types) do
- encoded_data
- |> Base.decode16!(case: :mixed)
- |> TypeDecoder.decode_raw(types)
- end
end
diff --git a/apps/indexer/lib/indexer/transform/transaction_actions.ex b/apps/indexer/lib/indexer/transform/transaction_actions.ex
index 549be9fa80cb..618c311f2a95 100644
--- a/apps/indexer/lib/indexer/transform/transaction_actions.ex
+++ b/apps/indexer/lib/indexer/transform/transaction_actions.ex
@@ -6,22 +6,24 @@ defmodule Indexer.Transform.TransactionActions do
require Logger
import Ecto.Query, only: [from: 2]
+ import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
+ import Explorer.Helper, only: [decode_data: 2]
- alias ABI.TypeDecoder
alias Explorer.Chain.Cache.NetVersion
alias Explorer.Chain.Cache.{TransactionActionTokensData, TransactionActionUniswapPools}
- alias Explorer.Chain.{Address, Data, Hash, Token, TransactionAction}
+ alias Explorer.Chain.{Address, Hash, Token, TransactionAction}
alias Explorer.Repo
alias Explorer.SmartContract.Reader
+ alias Indexer.Helper
@mainnet 1
@goerli 5
@optimism 10
@polygon 137
+ @base_mainnet 8453
@base_goerli 84531
# @gnosis 100
- @burn_address "0x0000000000000000000000000000000000000000"
@uniswap_v3_factory_abi [
%{
"inputs" => [
@@ -161,7 +163,7 @@ defmodule Indexer.Transform.TransactionActions do
end
defp parse_uniswap_v3(logs, actions, protocols_to_rewrite, chain_id) do
- if Enum.member?([@mainnet, @goerli, @optimism, @polygon, @base_goerli], chain_id) and
+ if Enum.member?([@mainnet, @goerli, @optimism, @polygon, @base_mainnet, @base_goerli], chain_id) and
(is_nil(protocols_to_rewrite) or Enum.empty?(protocols_to_rewrite) or
Enum.member?(protocols_to_rewrite, "uniswap_v3")) do
uniswap_v3_positions_nft =
@@ -193,7 +195,7 @@ defmodule Indexer.Transform.TransactionActions do
@aave_v3_liquidation_call_event
],
sanitize_first_topic(log.first_topic)
- ) && address_hash_to_string(log.address_hash) == pool_address
+ ) && Helper.address_hash_to_string(log.address_hash, true) == pool_address
end)
end
@@ -283,8 +285,15 @@ defmodule Indexer.Transform.TransactionActions do
[debt_amount, collateral_amount, _liquidator, _receive_a_token] =
decode_data(log.data, [{:uint, 256}, {:uint, 256}, :address, :bool])
- debt_address = truncate_address_hash(log.third_topic)
- collateral_address = truncate_address_hash(log.second_topic)
+ debt_address =
+ log.third_topic
+ |> Helper.log_topic_to_string()
+ |> truncate_address_hash()
+
+ collateral_address =
+ log.second_topic
+ |> Helper.log_topic_to_string()
+ |> truncate_address_hash()
case get_token_data([debt_address, collateral_address]) do
false ->
@@ -316,7 +325,10 @@ defmodule Indexer.Transform.TransactionActions do
defp aave_handle_event(type, amount, log, address_topic, chain_id)
when type in ["borrow", "supply", "withdraw", "repay", "flash_loan"] do
- address = truncate_address_hash(address_topic)
+ address =
+ address_topic
+ |> Helper.log_topic_to_string()
+ |> truncate_address_hash()
case get_token_data([address]) do
false ->
@@ -343,7 +355,10 @@ defmodule Indexer.Transform.TransactionActions do
end
defp aave_handle_event(type, log, address_topic, chain_id) when type in ["enable_collateral", "disable_collateral"] do
- address = truncate_address_hash(address_topic)
+ address =
+ address_topic
+ |> Helper.log_topic_to_string()
+ |> truncate_address_hash()
case get_token_data([address]) do
false ->
@@ -397,7 +412,7 @@ defmodule Indexer.Transform.TransactionActions do
first_topic
) ||
(first_topic == @uniswap_v3_transfer_nft_event &&
- address_hash_to_string(log.address_hash) == uniswap_v3_positions_nft)
+ Helper.address_hash_to_string(log.address_hash, true) == uniswap_v3_positions_nft)
end)
end
@@ -406,7 +421,7 @@ defmodule Indexer.Transform.TransactionActions do
with false <- first_topic == @uniswap_v3_transfer_nft_event,
# check UniswapV3Pool contract is legitimate
- pool_address <- address_hash_to_string(log.address_hash),
+ pool_address <- Helper.address_hash_to_string(log.address_hash, true),
false <- is_nil(legitimate[pool_address]),
false <- Enum.empty?(legitimate[pool_address]),
# this is legitimate uniswap pool, so handle this event
@@ -446,12 +461,23 @@ defmodule Indexer.Transform.TransactionActions do
|> Enum.reduce(%{}, fn log, acc ->
if sanitize_first_topic(log.first_topic) == @uniswap_v3_transfer_nft_event do
# This is Transfer event for NFT
- from = truncate_address_hash(log.second_topic)
+ from =
+ log.second_topic
+ |> Helper.log_topic_to_string()
+ |> truncate_address_hash()
# credo:disable-for-next-line
- if from == @burn_address do
- to = truncate_address_hash(log.third_topic)
- [token_id] = decode_data(log.fourth_topic, [{:uint, 256}])
+ if from == burn_address_hash_string() do
+ to =
+ log.third_topic
+ |> Helper.log_topic_to_string()
+ |> truncate_address_hash()
+
+ [token_id] =
+ log.fourth_topic
+ |> Helper.log_topic_to_string()
+ |> decode_data([{:uint, 256}])
+
mint_nft_ids = Map.put_new(acc, to, %{ids: [], log_index: log.index})
Map.put(mint_nft_ids, to, %{
@@ -585,7 +611,7 @@ defmodule Indexer.Transform.TransactionActions do
sanitize_first_topic(log.first_topic) != @uniswap_v3_transfer_nft_event
end)
|> Enum.reduce(addresses_acc, fn log, acc ->
- pool_address = address_hash_to_string(log.address_hash)
+ pool_address = Helper.address_hash_to_string(log.address_hash, true)
Map.put(acc, pool_address, true)
end)
end)
@@ -650,8 +676,12 @@ defmodule Indexer.Transform.TransactionActions do
end
end)
|> Enum.map(fn {pool_address, pool} ->
- token0 = if is_address_correct?(pool.token0), do: String.downcase(pool.token0), else: @burn_address
- token1 = if is_address_correct?(pool.token1), do: String.downcase(pool.token1), else: @burn_address
+ token0 =
+ if Helper.is_address_correct?(pool.token0), do: String.downcase(pool.token0), else: burn_address_hash_string()
+
+ token1 =
+ if Helper.is_address_correct?(pool.token1), do: String.downcase(pool.token1), else: burn_address_hash_string()
+
fee = if pool.fee == "", do: 0, else: pool.fee
# we will call getPool(token0, token1, fee) public getter
@@ -759,22 +789,6 @@ defmodule Indexer.Transform.TransactionActions do
end)
end
- defp decode_data("0x", types) do
- for _ <- types, do: nil
- end
-
- defp decode_data("0x" <> encoded_data, types) do
- encoded_data
- |> Base.decode16!(case: :mixed)
- |> TypeDecoder.decode_raw(types)
- end
-
- defp decode_data(%Data{} = data, types) do
- data
- |> Data.to_string()
- |> decode_data(types)
- end
-
defp fractional(%Decimal{} = amount, %Decimal{} = decimals) do
amount.sign
|> Decimal.new(amount.coef, amount.exp - Decimal.to_integer(decimals))
@@ -948,21 +962,6 @@ defmodule Indexer.Transform.TransactionActions do
{requests, responses}
end
- defp is_address_correct?(address) do
- String.match?(address, ~r/^0x[[:xdigit:]]{40}$/i)
- end
-
- defp address_hash_to_string(hash) do
- address_string =
- if is_binary(hash) do
- hash
- else
- Hash.to_string(hash)
- end
-
- String.downcase(address_string)
- end
-
defp logs_group_by_txs(logs) do
logs
|> Enum.group_by(& &1.transaction_hash)
@@ -995,10 +994,10 @@ defmodule Indexer.Transform.TransactionActions do
end
defp sanitize_first_topic(first_topic) do
- if is_nil(first_topic), do: "", else: String.downcase(first_topic)
+ if is_nil(first_topic), do: "", else: String.downcase(Helper.log_topic_to_string(first_topic))
end
- defp truncate_address_hash(nil), do: @burn_address
+ defp truncate_address_hash(nil), do: burn_address_hash_string()
defp truncate_address_hash("0x000000000000000000000000" <> truncated_hash) do
"0x#{truncated_hash}"
diff --git a/apps/indexer/mix.exs b/apps/indexer/mix.exs
index ae700e1ecf30..7e349261ccbd 100644
--- a/apps/indexer/mix.exs
+++ b/apps/indexer/mix.exs
@@ -14,7 +14,7 @@ defmodule Indexer.MixProject do
elixirc_paths: elixirc_paths(Mix.env()),
lockfile: "../../mix.lock",
start_permanent: Mix.env() == :prod,
- version: "5.1.5"
+ version: "6.0.0"
]
end
@@ -45,8 +45,8 @@ defmodule Indexer.MixProject do
{:ex_rlp, "~> 0.6.0"},
# Importing to database
{:explorer, in_umbrella: true},
- # libsecp2561k1 crypto functions
- {:libsecp256k1, "~> 0.1.10"},
+ # ex_secp256k1 crypto functions
+ {:ex_secp256k1, "~> 0.7.0"},
# Log errors and application output to separate files
{:logger_file_backend, "~> 0.0.10"},
# Mocking `EthereumJSONRPC.Transport`, so we avoid hitting real chains for local testing
@@ -55,7 +55,8 @@ defmodule Indexer.MixProject do
# Tracing
{:spandex, "~> 3.0"},
# `:spandex` integration with Datadog
- {:spandex_datadog, "~> 1.0"}
+ {:spandex_datadog, "~> 1.0"},
+ {:logger_json, "~> 5.1"}
]
end
diff --git a/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs b/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs
index e485b45c5321..774ebfe3a5ab 100644
--- a/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs
+++ b/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs
@@ -8,7 +8,6 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisorTest do
alias Explorer.Chain.Block
alias Explorer.Repo
- alias Explorer.Utility.MissingRangesManipulator
alias Indexer.BoundInterval
alias Indexer.Block.Catchup
alias Indexer.Block.Catchup.MissingRangesCollector
@@ -51,37 +50,42 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisorTest do
EthereumJSONRPC.Mox
|> stub(:json_rpc, fn
# latest block number to seed starting block number for genesis and realtime tasks
- %{method: "eth_getBlockByNumber", params: ["latest", false]}, _options ->
+ [%{id: id, method: "eth_getBlockByNumber", params: ["latest", false]}], _options ->
{:ok,
- %{
- "author" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381",
- "difficulty" => "0xfffffffffffffffffffffffffffffffe",
- "extraData" => "0xd583010a068650617269747986312e32362e32826c69",
- "gasLimit" => "0x7a1200",
- "gasUsed" => "0x0",
- "hash" => "0x627baabf5a17c0cfc547b6903ac5e19eaa91f30d9141be1034e3768f6adbc94e",
- "logsBloom" =>
- "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
- "miner" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381",
- "number" => block_quantity,
- "parentHash" => "0x006edcaa1e6fde822908783bc4ef1ad3675532d542fce53537557391cfe34c3c",
- "receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
- "sealFields" => [
- "0x841240b30d",
- "0xb84158bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01"
- ],
- "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
- "signature" =>
- "58bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01",
- "size" => "0x243",
- "stateRoot" => "0x9a8111062667f7b162851a1cbbe8aece5ff12e761b3dcee93b787fcc12548cf7",
- "step" => "306230029",
- "timestamp" => "0x5b437f41",
- "totalDifficulty" => "0x342337ffffffffffffffffffffffffed8d29bb",
- "transactions" => [],
- "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
- "uncles" => []
- }}
+ [
+ %{
+ id: id,
+ result: %{
+ "author" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381",
+ "difficulty" => "0xfffffffffffffffffffffffffffffffe",
+ "extraData" => "0xd583010a068650617269747986312e32362e32826c69",
+ "gasLimit" => "0x7a1200",
+ "gasUsed" => "0x0",
+ "hash" => "0x627baabf5a17c0cfc547b6903ac5e19eaa91f30d9141be1034e3768f6adbc94e",
+ "logsBloom" =>
+ "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "miner" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381",
+ "number" => block_quantity,
+ "parentHash" => "0x006edcaa1e6fde822908783bc4ef1ad3675532d542fce53537557391cfe34c3c",
+ "receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
+ "sealFields" => [
+ "0x841240b30d",
+ "0xb84158bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01"
+ ],
+ "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
+ "signature" =>
+ "58bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01",
+ "size" => "0x243",
+ "stateRoot" => "0x9a8111062667f7b162851a1cbbe8aece5ff12e761b3dcee93b787fcc12548cf7",
+ "step" => "306230029",
+ "timestamp" => "0x5b437f41",
+ "totalDifficulty" => "0x342337ffffffffffffffffffffffffed8d29bb",
+ "transactions" => [],
+ "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
+ "uncles" => []
+ }
+ }
+ ]}
[%{method: "trace_block"} | _] = requests, _options ->
{:ok, Enum.map(requests, fn %{id: id} -> %{id: id, result: []} end)}
@@ -533,6 +537,8 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisorTest do
# from `setup :state`
assert_received :catchup_index
+ Process.sleep(50)
+
assert {:noreply,
%Catchup.BoundIntervalSupervisor{fetcher: %Catchup.Fetcher{}, task: %Task{pid: pid, ref: ref}} =
catchup_index_state} = Catchup.BoundIntervalSupervisor.handle_info(:catchup_index, state)
diff --git a/apps/indexer/test/indexer/block/catchup/fetcher_test.exs b/apps/indexer/test/indexer/block/catchup/fetcher_test.exs
index c667429ef0ee..93f687907ca0 100644
--- a/apps/indexer/test/indexer/block/catchup/fetcher_test.exs
+++ b/apps/indexer/test/indexer/block/catchup/fetcher_test.exs
@@ -9,6 +9,7 @@ defmodule Indexer.Block.Catchup.FetcherTest do
alias Explorer.Chain.Block.Reward
alias Explorer.Chain.Hash
alias Explorer.Utility.MissingRangesManipulator
+ alias Explorer.Utility.MissingBlockRange
alias Indexer.Block
alias Indexer.Block.Catchup.Fetcher
alias Indexer.Block.Catchup.MissingRangesCollector
@@ -37,7 +38,7 @@ defmodule Indexer.Block.Catchup.FetcherTest do
describe "import/1" do
setup do
configuration = Application.get_env(:indexer, :last_block)
- Application.put_env(:indexer, :last_block, "0")
+ Application.put_env(:indexer, :last_block, 0)
on_exit(fn ->
Application.put_env(:indexer, :last_block, configuration)
@@ -285,6 +286,8 @@ defmodule Indexer.Block.Catchup.FetcherTest do
assert count(Chain.Block) == 0
+ Process.sleep(50)
+
assert %{first_block_number: ^block_number, last_block_number: 0, missing_block_count: 2, shrunk: false} =
Fetcher.task(%Fetcher{
block_fetcher: %Block.Fetcher{
@@ -438,6 +441,8 @@ defmodule Indexer.Block.Catchup.FetcherTest do
Process.register(pid, BlockReward)
+ Process.sleep(50)
+
assert %{first_block_number: ^block_number, last_block_number: 0, missing_block_count: 2, shrunk: false} =
Fetcher.task(%Fetcher{
block_fetcher: %Block.Fetcher{
@@ -586,6 +591,8 @@ defmodule Indexer.Block.Catchup.FetcherTest do
Process.register(pid, BlockReward)
+ Process.sleep(50)
+
assert %{first_block_number: ^block_number, last_block_number: 0, missing_block_count: 2, shrunk: false} =
Fetcher.task(%Fetcher{
block_fetcher: %Block.Fetcher{
@@ -600,6 +607,63 @@ defmodule Indexer.Block.Catchup.FetcherTest do
assert_receive {:block_numbers, [^block_number]}, 5_000
end
+
+ test "failed blocks handles correctly", %{json_rpc_named_arguments: json_rpc_named_arguments} do
+ Application.put_env(:indexer, Indexer.Block.Catchup.Fetcher, batch_size: 2, concurrency: 10)
+ Application.put_env(:indexer, :block_ranges, "0..1")
+ start_supervised!({Task.Supervisor, name: Indexer.Block.Catchup.TaskSupervisor})
+ MissingRangesCollector.start_link([])
+ MissingRangesManipulator.start_link([])
+
+ EthereumJSONRPC.Mox
+ |> expect(:json_rpc, 2, fn
+ [
+ %{
+ id: id_1,
+ jsonrpc: "2.0",
+ method: "eth_getBlockByNumber",
+ params: ["0x1", true]
+ },
+ %{
+ id: id_2,
+ jsonrpc: "2.0",
+ method: "eth_getBlockByNumber",
+ params: ["0x0", true]
+ }
+ ],
+ _options ->
+ {:ok,
+ [
+ %{
+ id: id_1,
+ jsonrpc: "2.0",
+ error: %{message: "error"}
+ },
+ %{
+ id: id_2,
+ jsonrpc: "2.0",
+ error: %{message: "error"}
+ }
+ ]}
+
+ [], _options ->
+ {:ok, []}
+ end)
+
+ Process.sleep(50)
+
+ assert %{first_block_number: 1, last_block_number: 0, missing_block_count: 2, shrunk: false} =
+ Fetcher.task(%Fetcher{
+ block_fetcher: %Block.Fetcher{
+ callback_module: Fetcher,
+ json_rpc_named_arguments: json_rpc_named_arguments
+ }
+ })
+
+ Process.sleep(1000)
+
+ assert %{from_number: 1, to_number: 0} = Repo.one(MissingBlockRange)
+ end
end
defp count(schema) do
diff --git a/apps/indexer/test/indexer/block/catchup/missing_ranges_collector_test.exs b/apps/indexer/test/indexer/block/catchup/missing_ranges_collector_test.exs
index 72436eb04132..dd36cd1e9ec9 100644
--- a/apps/indexer/test/indexer/block/catchup/missing_ranges_collector_test.exs
+++ b/apps/indexer/test/indexer/block/catchup/missing_ranges_collector_test.exs
@@ -14,7 +14,7 @@ defmodule Indexer.Block.Catchup.MissingRangesCollectorTest do
insert(:block, number: 1_000_000)
insert(:block, number: 500_123)
MissingRangesCollector.start_link([])
- Process.sleep(500)
+ Process.sleep(1000)
assert [999_999..900_000//-1] = batch = MissingBlockRange.get_latest_batch(1)
MissingBlockRange.clear_batch(batch)
@@ -36,8 +36,8 @@ defmodule Indexer.Block.Catchup.MissingRangesCollectorTest do
end
test "FIRST_BLOCK and LAST_BLOCK envs" do
- Application.put_env(:indexer, :first_block, "100")
- Application.put_env(:indexer, :last_block, "200")
+ Application.put_env(:indexer, :first_block, 100)
+ Application.put_env(:indexer, :last_block, 200)
insert(:missing_block_range, from_number: 250, to_number: 220)
insert(:missing_block_range, from_number: 220, to_number: 190)
diff --git a/apps/indexer/test/indexer/block/fetcher/receipts_test.exs b/apps/indexer/test/indexer/block/fetcher/receipts_test.exs
index c70d10761a01..a7c45106551e 100644
--- a/apps/indexer/test/indexer/block/fetcher/receipts_test.exs
+++ b/apps/indexer/test/indexer/block/fetcher/receipts_test.exs
@@ -51,8 +51,7 @@ defmodule Indexer.Block.Fetcher.ReceiptsTest do
"topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"],
"transactionHash" => "0x43bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
"transactionIndex" => "0x0",
- "transactionLogIndex" => "0x0",
- "type" => "mined"
+ "transactionLogIndex" => "0x0"
}
],
"logsBloom" =>
@@ -82,8 +81,7 @@ defmodule Indexer.Block.Fetcher.ReceiptsTest do
"topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"],
"transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
"transactionIndex" => "0x0",
- "transactionLogIndex" => "0x0",
- "type" => "mined"
+ "transactionLogIndex" => "0x0"
}
],
"logsBloom" =>
diff --git a/apps/indexer/test/indexer/block/fetcher_test.exs b/apps/indexer/test/indexer/block/fetcher_test.exs
index 24be0dfa8803..0498cfd37ecb 100644
--- a/apps/indexer/test/indexer/block/fetcher_test.exs
+++ b/apps/indexer/test/indexer/block/fetcher_test.exs
@@ -375,8 +375,7 @@ defmodule Indexer.Block.FetcherTest do
"topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"],
"transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
"transactionIndex" => "0x0",
- "transactionLogIndex" => "0x0",
- "type" => "mined"
+ "transactionLogIndex" => "0x0"
}
],
"logsBloom" =>
diff --git a/apps/indexer/test/indexer/block/realtime/fetcher_test.exs b/apps/indexer/test/indexer/block/realtime/fetcher_test.exs
index 155fa52c243d..9d0459e915bc 100644
--- a/apps/indexer/test/indexer/block/realtime/fetcher_test.exs
+++ b/apps/indexer/test/indexer/block/realtime/fetcher_test.exs
@@ -472,30 +472,32 @@ defmodule Indexer.Block.Realtime.FetcherTest do
]}
[
- %{
- id: 0,
- jsonrpc: "2.0",
- method: "eth_getBalance",
- params: ["0x40b18103537c0f15d5e137dd8ddd019b84949d16", "0x3C365F"]
- },
- %{
- id: 1,
- jsonrpc: "2.0",
- method: "eth_getBalance",
- params: ["0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", "0x3C365F"]
- },
- %{
- id: 2,
- jsonrpc: "2.0",
- method: "eth_getBalance",
- params: ["0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3", "0x3C3660"]
- },
- %{
- id: 3,
- jsonrpc: "2.0",
- method: "eth_getBalance",
- params: ["0x698bf6943bab687b2756394624aa183f434f65da", "0x3C365F"]
- }
+ [
+ %{
+ id: 0,
+ jsonrpc: "2.0",
+ method: "eth_getBalance",
+ params: ["0x40b18103537c0f15d5e137dd8ddd019b84949d16", "0x3C365F"]
+ },
+ %{
+ id: 1,
+ jsonrpc: "2.0",
+ method: "eth_getBalance",
+ params: ["0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", "0x3C365F"]
+ },
+ %{
+ id: 2,
+ jsonrpc: "2.0",
+ method: "eth_getBalance",
+ params: ["0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3", "0x3C3660"]
+ },
+ %{
+ id: 3,
+ jsonrpc: "2.0",
+ method: "eth_getBalance",
+ params: ["0x698bf6943bab687b2756394624aa183f434f65da", "0x3C365F"]
+ }
+ ]
],
_ ->
{:ok,
@@ -964,30 +966,32 @@ defmodule Indexer.Block.Realtime.FetcherTest do
]}
[
- %{
- id: 0,
- jsonrpc: "2.0",
- method: "eth_getBalance",
- params: ["0x40b18103537c0f15d5e137dd8ddd019b84949d16", "0x3C365F"]
- },
- %{
- id: 1,
- jsonrpc: "2.0",
- method: "eth_getBalance",
- params: ["0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", "0x3C365F"]
- },
- %{
- id: 2,
- jsonrpc: "2.0",
- method: "eth_getBalance",
- params: ["0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3", "0x3C3660"]
- },
- %{
- id: 3,
- jsonrpc: "2.0",
- method: "eth_getBalance",
- params: ["0x698bf6943bab687b2756394624aa183f434f65da", "0x3C365F"]
- }
+ [
+ %{
+ id: 0,
+ jsonrpc: "2.0",
+ method: "eth_getBalance",
+ params: ["0x40b18103537c0f15d5e137dd8ddd019b84949d16", "0x3C365F"]
+ },
+ %{
+ id: 1,
+ jsonrpc: "2.0",
+ method: "eth_getBalance",
+ params: ["0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", "0x3C365F"]
+ },
+ %{
+ id: 2,
+ jsonrpc: "2.0",
+ method: "eth_getBalance",
+ params: ["0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3", "0x3C3660"]
+ },
+ %{
+ id: 3,
+ jsonrpc: "2.0",
+ method: "eth_getBalance",
+ params: ["0x698bf6943bab687b2756394624aa183f434f65da", "0x3C365F"]
+ }
+ ]
],
_ ->
{:ok,
@@ -1183,23 +1187,27 @@ defmodule Indexer.Block.Realtime.FetcherTest do
]}
[
- %{
- id: 0,
- jsonrpc: "2.0",
- method: "eth_getBalance",
- params: ["0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", "0x3C365F"]
- }
+ [
+ %{
+ id: 0,
+ jsonrpc: "2.0",
+ method: "eth_getBalance",
+ params: ["0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", "0x3C365F"]
+ }
+ ]
],
_ ->
{:ok, [%{id: 0, jsonrpc: "2.0", result: "0x53474fa377a46000"}]}
[
- %{
- id: 0,
- jsonrpc: "2.0",
- method: "eth_getBalance",
- params: ["0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3", "0x3C3660"]
- }
+ [
+ %{
+ id: 0,
+ jsonrpc: "2.0",
+ method: "eth_getBalance",
+ params: ["0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3", "0x3C3660"]
+ }
+ ]
],
_ ->
{:ok, [%{id: 0, jsonrpc: "2.0", result: "0x53507afe51f28000"}]}
@@ -1224,12 +1232,14 @@ defmodule Indexer.Block.Realtime.FetcherTest do
]}
[
- %{
- id: 0,
- jsonrpc: "2.0",
- method: "eth_getBalance",
- params: ["0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", "0x3C365F"]
- }
+ [
+ %{
+ id: 0,
+ jsonrpc: "2.0",
+ method: "eth_getBalance",
+ params: ["0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", "0x3C365F"]
+ }
+ ]
],
_ ->
{:ok, [%{id: 0, jsonrpc: "2.0", result: "0x53474fa377a46000"}]}
diff --git a/apps/indexer/test/indexer/buffered_task_test.exs b/apps/indexer/test/indexer/buffered_task_test.exs
index 7e30c484d849..2d41b5d7a070 100644
--- a/apps/indexer/test/indexer/buffered_task_test.exs
+++ b/apps/indexer/test/indexer/buffered_task_test.exs
@@ -28,7 +28,8 @@ defmodule Indexer.BufferedTaskTest do
task_supervisor: BufferedTaskSup,
flush_interval: @flush_interval,
max_batch_size: max_batch_size,
- max_concurrency: 2}
+ max_concurrency: 2,
+ poll: false}
]}
)
end
diff --git a/apps/indexer/test/indexer/fetcher/coin_balance_on_demand_test.exs b/apps/indexer/test/indexer/fetcher/coin_balance_on_demand_test.exs
index 9ca780a58ff5..d345ecee0827 100644
--- a/apps/indexer/test/indexer/fetcher/coin_balance_on_demand_test.exs
+++ b/apps/indexer/test/indexer/fetcher/coin_balance_on_demand_test.exs
@@ -5,7 +5,9 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do
use Explorer.DataCase
import Mox
+ import EthereumJSONRPC, only: [integer_to_quantity: 1]
+ alias Explorer.Chain
alias Explorer.Chain.Events.Subscriber
alias Explorer.Chain.Wei
alias Explorer.Counters.AverageBlockTime
@@ -82,6 +84,52 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do
end
end
+ describe "trigger_historic_fetch/2" do
+ test "fetches and imports balance for any block" do
+ address = insert(:address)
+ block = insert(:block)
+ insert(:block)
+ string_address_hash = to_string(address.hash)
+ block_number = block.number
+ string_block_number = integer_to_quantity(block_number)
+ balance = 42
+ assert nil == Chain.get_coin_balance(address.hash, block_number)
+
+ EthereumJSONRPC.Mox
+ |> expect(:json_rpc, fn [
+ %{
+ id: id,
+ method: "eth_getBalance",
+ params: [^string_address_hash, ^string_block_number]
+ }
+ ],
+ _options ->
+ {:ok, [%{id: id, jsonrpc: "2.0", result: integer_to_quantity(balance)}]}
+ end)
+
+ EthereumJSONRPC.Mox
+ |> expect(:json_rpc, fn [
+ %{
+ id: id,
+ jsonrpc: "2.0",
+ method: "eth_getBlockByNumber",
+ params: [^string_block_number, true]
+ }
+ ],
+ _ ->
+ {:ok, [eth_block_number_fake_response(string_block_number, id)]}
+ end)
+
+ {:ok, expected_wei} = Wei.cast(balance)
+
+ CoinBalanceOnDemand.trigger_historic_fetch(address.hash, block_number)
+
+ :timer.sleep(1000)
+
+ assert %{value: ^expected_wei} = Chain.get_coin_balance(address.hash, block_number)
+ end
+ end
+
describe "update behaviour" do
setup do
Subscriber.to(:addresses, :on_demand)
@@ -184,9 +232,9 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do
end
end
- defp eth_block_number_fake_response(block_quantity) do
+ defp eth_block_number_fake_response(block_quantity, id \\ 0) do
%{
- id: 0,
+ id: id,
jsonrpc: "2.0",
result: %{
"author" => "0x0000000000000000000000000000000000000000",
diff --git a/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs b/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs
index b36006b872f6..e451525869b1 100644
--- a/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs
+++ b/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs
@@ -5,8 +5,10 @@ defmodule Indexer.Fetcher.InternalTransactionTest do
import ExUnit.CaptureLog
import Mox
- alias Explorer.Chain
- alias Explorer.Chain.PendingBlockOperation
+ alias Ecto.Multi
+ alias Explorer.{Chain, Repo}
+ alias Explorer.Chain.{Block, PendingBlockOperation}
+ alias Explorer.Chain.Import.Runner.Blocks
alias Indexer.Fetcher.{CoinBalance, InternalTransaction, PendingTransaction}
# MUST use global mode because we aren't guaranteed to get PendingTransactionFetcher's pid back fast enough to `allow`
@@ -466,4 +468,39 @@ defmodule Indexer.Fetcher.InternalTransactionTest do
assert logs =~ "foreign_key_violation on internal transactions import, foreign transactions hashes:"
end
end
+
+ test "doesn't delete pending block operations after block import if no async process was requested", %{
+ json_rpc_named_arguments: json_rpc_named_arguments
+ } do
+ fetcher_options =
+ Keyword.merge([poll: true, json_rpc_named_arguments: json_rpc_named_arguments], InternalTransaction.defaults())
+
+ if fetcher_options[:poll] do
+ expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options ->
+ {:ok, [%{id: id, result: []}]}
+ end)
+ end
+
+ InternalTransaction.Supervisor.Case.start_supervised!(fetcher_options)
+
+ %Ecto.Changeset{valid?: true, changes: block_changes} =
+ Block.changeset(%Block{}, params_for(:block, miner_hash: insert(:address).hash, number: 1))
+
+ changes_list = [block_changes]
+ timestamp = DateTime.utc_now()
+ options = %{timestamps: %{inserted_at: timestamp, updated_at: timestamp}}
+
+ assert [] = Repo.all(PendingBlockOperation)
+
+ {:ok, %{blocks: [%{number: block_number, hash: block_hash}]}} =
+ Multi.new()
+ |> Blocks.run(changes_list, options)
+ |> Repo.transaction()
+
+ assert %{block_number: ^block_number, block_hash: ^block_hash} = Repo.one(PendingBlockOperation)
+
+ Process.sleep(4000)
+
+ assert %{block_number: ^block_number, block_hash: ^block_hash} = Repo.one(PendingBlockOperation)
+ end
end
diff --git a/apps/indexer/test/indexer/fetcher/pending_block_operations_sanitizer_test.ex b/apps/indexer/test/indexer/fetcher/pending_block_operations_sanitizer_test.exs
similarity index 100%
rename from apps/indexer/test/indexer/fetcher/pending_block_operations_sanitizer_test.ex
rename to apps/indexer/test/indexer/fetcher/pending_block_operations_sanitizer_test.exs
diff --git a/apps/indexer/test/indexer/fetcher/rootstock_data_test.exs b/apps/indexer/test/indexer/fetcher/rootstock_data_test.exs
new file mode 100644
index 000000000000..d7954ce886b8
--- /dev/null
+++ b/apps/indexer/test/indexer/fetcher/rootstock_data_test.exs
@@ -0,0 +1,136 @@
+defmodule Indexer.Fetcher.RootstockDataTest do
+ use EthereumJSONRPC.Case
+ use Explorer.DataCase
+
+ import Mox
+ import EthereumJSONRPC, only: [integer_to_quantity: 1]
+
+ alias Indexer.Fetcher.RootstockData
+
+ setup :verify_on_exit!
+ setup :set_mox_global
+
+ if Application.compile_env(:explorer, :chain_type) == "rsk" do
+ test "do not start when all old blocks are fetched", %{json_rpc_named_arguments: json_rpc_named_arguments} do
+ RootstockData.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
+
+ :timer.sleep(300)
+
+ assert [{Indexer.Fetcher.RootstockData, :undefined, :worker, [Indexer.Fetcher.RootstockData]} | _] =
+ RootstockData.Supervisor |> Supervisor.which_children()
+ end
+
+ test "stops when all old blocks are fetched", %{json_rpc_named_arguments: json_rpc_named_arguments} do
+ block_a = insert(:block)
+ block_b = insert(:block)
+
+ block_a_number_string = integer_to_quantity(block_a.number)
+ block_b_number_string = integer_to_quantity(block_b.number)
+
+ EthereumJSONRPC.Mox
+ |> stub(:json_rpc, fn requests, _options ->
+ {:ok,
+ Enum.map(requests, fn
+ %{id: id, method: "eth_getBlockByNumber", params: [^block_a_number_string, false]} ->
+ %{
+ id: id,
+ result: %{
+ "author" => "0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c",
+ "difficulty" => "0x6bc767dd80781",
+ "extraData" => "0x5050594520737061726b706f6f6c2d6574682d7477",
+ "gasLimit" => "0x7a121d",
+ "gasUsed" => "0x79cbe9",
+ "hash" => to_string(block_a.hash),
+ "logsBloom" =>
+ "0x044d42d008801488400e1809190200a80d06105bc0c4100b047895c0d518327048496108388040140010b8208006288102e206160e21052322440924002090c1c808a0817405ab238086d028211014058e949401012403210314896702d06880c815c3060a0f0809987c81044488292cc11d57882c912a808ca10471c84460460040000c0001012804022000a42106591881d34407420ba401e1c08a8d00a000a34c11821a80222818a4102152c8a0c044032080c6462644223104d618e0e544072008120104408205c60510542264808488220403000106281a0290404220112c10b080145028c8000300b18a2c8280701c882e702210b00410834840108084",
+ "miner" => to_string(block_a.miner),
+ "mixHash" => "0xda53ae7c2b3c529783d6cdacdb90587fd70eb651c0f04253e8ff17de97844010",
+ "nonce" => "0x0946e5f01fce12bc",
+ "number" => block_a_number_string,
+ "parentHash" => to_string(block_a.parent_hash),
+ "receiptsRoot" => "0xa7d2b82bd8526de11736c18bd5cc8cfe2692106c4364526f3310ad56d78669c4",
+ "sealFields" => [
+ "0xa0da53ae7c2b3c529783d6cdacdb90587fd70eb651c0f04253e8ff17de97844010",
+ "0x880946e5f01fce12bc"
+ ],
+ "sha3Uncles" => "0x483a8a21a5825ad270f358b3ea56e060bbb8b3082d9a92ec8fa17a5c7e6fc1b6",
+ "size" => "0x544c",
+ "stateRoot" => "0x85daa9cd528004c1609d4cb3520fd958e85983bb4183124a4a9f7137fd39c691",
+ "timestamp" => "0x5c8bc76e",
+ "totalDifficulty" => "0x201a42c35142ae94458",
+ "transactions" => [],
+ "transactionsRoot" => "0xcd6c12fa43cd4e92ad5c0bf232b30488bbcbfe273c5b4af0366fced0767d54db",
+ "uncles" => [],
+ "withdrawals" => [],
+ "minimumGasPrice" => "0x0",
+ "bitcoinMergedMiningHeader" =>
+ "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9",
+ "bitcoinMergedMiningCoinbaseTransaction" =>
+ "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000",
+ "bitcoinMergedMiningMerkleProof" =>
+ "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7",
+ "hashForMergedMining" => "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40"
+ }
+ }
+
+ %{id: id, method: "eth_getBlockByNumber", params: [^block_b_number_string, false]} ->
+ %{
+ id: id,
+ result: %{
+ "author" => "0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c",
+ "difficulty" => "0x6bc767dd80781",
+ "extraData" => "0x5050594520737061726b706f6f6c2d6574682d7477",
+ "gasLimit" => "0x7a121d",
+ "gasUsed" => "0x79cbe9",
+ "hash" => to_string(block_b.hash),
+ "logsBloom" =>
+ "0x044d42d008801488400e1809190200a80d06105bc0c4100b047895c0d518327048496108388040140010b8208006288102e206160e21052322440924002090c1c808a0817405ab238086d028211014058e949401012403210314896702d06880c815c3060a0f0809987c81044488292cc11d57882c912a808ca10471c84460460040000c0001012804022000a42106591881d34407420ba401e1c08a8d00a000a34c11821a80222818a4102152c8a0c044032080c6462644223104d618e0e544072008120104408205c60510542264808488220403000106281a0290404220112c10b080145028c8000300b18a2c8280701c882e702210b00410834840108084",
+ "miner" => to_string(block_b.miner),
+ "mixHash" => "0xda53ae7c2b3c529783d6cdacdb90587fd70eb651c0f04253e8ff17de97844010",
+ "nonce" => "0x0946e5f01fce12bc",
+ "number" => block_b_number_string,
+ "parentHash" => to_string(block_b.parent_hash),
+ "receiptsRoot" => "0xa7d2b82bd8526de11736c18bd5cc8cfe2692106c4364526f3310ad56d78669c4",
+ "sealFields" => [
+ "0xa0da53ae7c2b3c529783d6cdacdb90587fd70eb651c0f04253e8ff17de97844010",
+ "0x880946e5f01fce12bc"
+ ],
+ "sha3Uncles" => "0x483a8a21a5825ad270f358b3ea56e060bbb8b3082d9a92ec8fa17a5c7e6fc1b6",
+ "size" => "0x544c",
+ "stateRoot" => "0x85daa9cd528004c1609d4cb3520fd958e85983bb4183124a4a9f7137fd39c691",
+ "timestamp" => "0x5c8bc76e",
+ "totalDifficulty" => "0x201a42c35142ae94458",
+ "transactions" => [],
+ "transactionsRoot" => "0xcd6c12fa43cd4e92ad5c0bf232b30488bbcbfe273c5b4af0366fced0767d54db",
+ "uncles" => [],
+ "withdrawals" => [],
+ "minimumGasPrice" => "0x1",
+ "bitcoinMergedMiningHeader" =>
+ "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9",
+ "bitcoinMergedMiningCoinbaseTransaction" =>
+ "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000",
+ "bitcoinMergedMiningMerkleProof" =>
+ "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7",
+ "hashForMergedMining" => "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40"
+ }
+ }
+ end)}
+ end)
+
+ pid = RootstockData.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
+
+ assert [{Indexer.Fetcher.RootstockData, worker_pid, :worker, [Indexer.Fetcher.RootstockData]} | _] =
+ RootstockData.Supervisor |> Supervisor.which_children()
+
+ assert is_pid(worker_pid)
+
+ :timer.sleep(300)
+
+ assert [{Indexer.Fetcher.RootstockData, :undefined, :worker, [Indexer.Fetcher.RootstockData]} | _] =
+ RootstockData.Supervisor |> Supervisor.which_children()
+
+ # Terminates the process so it finishes all Ecto processes.
+ GenServer.stop(pid)
+ end
+ end
+end
diff --git a/apps/indexer/test/indexer/fetcher/token_instance/helper_test.exs b/apps/indexer/test/indexer/fetcher/token_instance/helper_test.exs
new file mode 100644
index 000000000000..8d9987f9de24
--- /dev/null
+++ b/apps/indexer/test/indexer/fetcher/token_instance/helper_test.exs
@@ -0,0 +1,178 @@
+defmodule Indexer.Fetcher.TokenInstance.HelperTest do
+ use EthereumJSONRPC.Case
+ use Explorer.DataCase
+
+ alias Explorer.Chain.Token.Instance
+ alias EthereumJSONRPC.Encoder
+ alias Indexer.Fetcher.TokenInstance.Helper
+ alias Plug.Conn
+
+ import Mox
+
+ setup :verify_on_exit!
+ setup :set_mox_global
+
+ setup do
+ bypass = Bypass.open()
+
+ {:ok, bypass: bypass}
+ end
+
+ describe "fetch instance tests" do
+ test "fetches json metadata for kitties" do
+ Application.put_env(:explorer, :http_adapter, Explorer.Mox.HTTPoison)
+
+ result =
+ "{\"id\":100500,\"name\":\"KittyBlue_2_Lemonade\",\"generation\":20,\"genes\":\"623509754227292470437941473598751240781530569131665917719736997423495595\",\"created_at\":\"2017-12-06T01:56:27.000Z\",\"birthday\":\"2017-12-06T00:00:00.000Z\",\"image_url\":\"https://img.cryptokitties.co/0x06012c8cf97bead5deae237070f9587f8e7a266d/100500.svg\",\"image_url_cdn\":\"https://img.cn.cryptokitties.co/0x06012c8cf97bead5deae237070f9587f8e7a266d/100500.svg\",\"color\":\"strawberry\",\"background_color\":\"#ffe0e5\",\"bio\":\"Shalom! I'm KittyBlue_2_Lemonade. I'm a professional Foreign Film Director and I love cantaloupe. I'm convinced that the world is flat. One day I'll prove it. It's pawesome to meet you!\",\"kitty_type\":null,\"is_fancy\":false,\"is_exclusive\":false,\"is_special_edition\":false,\"fancy_type\":null,\"language\":\"en\",\"is_prestige\":false,\"prestige_type\":null,\"prestige_ranking\":null,\"prestige_time_limit\":null,\"status\":{\"is_ready\":true,\"is_gestating\":false,\"cooldown\":1410310201506,\"dynamic_cooldown\":1475064986478,\"cooldown_index\":10,\"cooldown_end_block\":0,\"pending_tx_type\":null,\"pending_tx_since\":null},\"purrs\":{\"count\":1,\"is_purred\":false},\"watchlist\":{\"count\":0,\"is_watchlisted\":false},\"hatcher\":{\"address\":\"0x7b9ea9ac69b8fde875554321472c732eeff06ca0\",\"image\":\"14\",\"nickname\":\"KittyBlu\",\"hasDapper\":false,\"twitter_id\":null,\"twitter_image_url\":null,\"twitter_handle\":null},\"auction\":{},\"offer\":{},\"owner\":{\"address\":\"0x7b9ea9ac69b8fde875554321472c732eeff06ca0\",\"hasDapper\":false,\"twitter_id\":null,\"twitter_image_url\":null,\"twitter_handle\":null,\"image\":\"14\",\"nickname\":\"KittyBlu\"},\"matron\":{\"id\":46234,\"name\":\"KittyBlue_1_Limegreen\",\"generation\":10,\"enhanced_cattributes\":[{\"type\":\"body\",\"kittyId\":19631,\"position\":105,\"description\":\"cymric\"},{\"type\":\"coloreyes\",\"kittyId\":40356,\"position\":263,\"description\":\"limegreen\"},{\"type\":\"eyes\",\"kittyId\":3185,\"position\":16,\"description\":\"raisedbrow\"},{\"type\":\"pattern\",\"kittyId\":46234,\"position\":-1,\"description\":\"totesbasic\"},{\"type\":\"mouth\",\"kittyId\":46234,\"position\":-1,\"description\":\"happygokitty\"},{\"type\":\"colorprimary\",\"kittyId\":46234,\"position\":-1,\"description\":\"greymatter\"},{\"type\":\"colorsecondary\",\"kittyId\":46234,\"position\":-1,\"description\":\"lemonade\"},{\"type\":\"colortertiary\",\"kittyId\":46234,\"position\":-1,\"description\":\"granitegrey\"}],\"owner_wallet_address\":\"0x7b9ea9ac69b8fde875554321472c732eeff06ca0\",\"owner\":{\"address\":\"0x7b9ea9ac69b8fde875554321472c732eeff06ca0\"},\"created_at\":\"2017-12-03T21:29:17.000Z\",\"image_url\":\"https://img.cryptokitties.co/0x06012c8cf97bead5deae237070f9587f8e7a266d/46234.svg\",\"image_url_cdn\":\"https://img.cn.cryptokitties.co/0x06012c8cf97bead5deae237070f9587f8e7a266d/46234.svg\",\"color\":\"limegreen\",\"is_fancy\":false,\"kitty_type\":null,\"is_exclusive\":false,\"is_special_edition\":false,\"fancy_type\":null,\"status\":{\"is_ready\":true,\"is_gestating\":false,\"cooldown\":1486487069384},\"hatched\":true,\"wrapped\":false,\"image_url_png\":\"https://img.cryptokitties.co/0x06012c8cf97bead5deae237070f9587f8e7a266d/46234.png\"},\"sire\":{\"id\":82090,\"name\":null,\"generation\":19,\"enhanced_cattributes\":[{\"type\":\"body\",\"kittyId\":82090,\"position\":-1,\"description\":\"himalayan\"},{\"type\":\"coloreyes\",\"kittyId\":82090,\"position\":-1,\"description\":\"strawberry\"},{\"type\":\"eyes\",\"kittyId\":82090,\"position\":-1,\"description\":\"thicccbrowz\"},{\"type\":\"pattern\",\"kittyId\":82090,\"position\":-1,\"description\":\"totesbasic\"},{\"type\":\"mouth\",\"kittyId\":82090,\"position\":-1,\"description\":\"pouty\"},{\"type\":\"colorprimary\",\"kittyId\":82090,\"position\":-1,\"description\":\"aquamarine\"},{\"type\":\"colorsecondary\",\"kittyId\":82090,\"position\":-1,\"description\":\"chocolate\"},{\"type\":\"colortertiary\",\"kittyId\":82090,\"position\":-1,\"description\":\"granitegrey\"}],\"owner_wallet_address\":\"0x798fdad0cedc4b298fc7d53a982fa0c5f447eaa5\",\"owner\":{\"address\":\"0x798fdad0cedc4b298fc7d53a982fa0c5f447eaa5\"},\"created_at\":\"2017-12-05T06:30:05.000Z\",\"image_url\":\"https://img.cryptokitties.co/0x06012c8cf97bead5deae237070f9587f8e7a266d/82090.svg\",\"image_url_cdn\":\"https://img.cn.cryptokitties.co/0x06012c8cf97bead5deae237070f9587f8e7a266d/82090.svg\",\"color\":\"strawberry\",\"is_fancy\":false,\"is_exclusive\":false,\"is_special_edition\":false,\"fancy_type\":null,\"status\":{\"is_ready\":true,\"is_gestating\":false,\"cooldown\":1486619010030},\"kitty_type\":null,\"hatched\":true,\"wrapped\":false,\"image_url_png\":\"https://img.cryptokitties.co/0x06012c8cf97bead5deae237070f9587f8e7a266d/82090.png\"},\"children\":[],\"hatched\":true,\"wrapped\":false,\"enhanced_cattributes\":[{\"type\":\"colorprimary\",\"description\":\"greymatter\",\"position\":null,\"kittyId\":100500},{\"type\":\"coloreyes\",\"description\":\"strawberry\",\"position\":null,\"kittyId\":100500},{\"type\":\"body\",\"description\":\"himalayan\",\"position\":null,\"kittyId\":100500},{\"type\":\"colorsecondary\",\"description\":\"lemonade\",\"position\":null,\"kittyId\":100500},{\"type\":\"mouth\",\"description\":\"pouty\",\"position\":null,\"kittyId\":100500},{\"type\":\"pattern\",\"description\":\"totesbasic\",\"position\":null,\"kittyId\":100500},{\"type\":\"eyes\",\"description\":\"thicccbrowz\",\"position\":null,\"kittyId\":100500},{\"type\":\"colortertiary\",\"description\":\"kittencream\",\"position\":null,\"kittyId\":100500},{\"type\":\"secret\",\"description\":\"se5\",\"position\":-1,\"kittyId\":100500},{\"type\":\"purrstige\",\"description\":\"pu20\",\"position\":-1,\"kittyId\":100500}],\"variation\":null,\"variation_ranking\":null,\"image_url_png\":\"https://img.cryptokitties.co/0x06012c8cf97bead5deae237070f9587f8e7a266d/100500.png\",\"items\":[]}"
+
+ Explorer.Mox.HTTPoison
+ |> expect(:get, fn "https://api.cryptokitties.co/kitties/100500", _headers, _options ->
+ {:ok, %HTTPoison.Response{status_code: 200, body: result}}
+ end)
+
+ insert(:token,
+ contract_address: build(:address, hash: "0x06012c8cf97bead5deae237070f9587f8e7a266d"),
+ type: "ERC-721"
+ )
+
+ [{:ok, %Instance{metadata: metadata}}] =
+ Helper.batch_fetch_instances([{"0x06012c8cf97bead5deae237070f9587f8e7a266d", 100_500}])
+
+ assert Map.get(metadata, "name") == "KittyBlue_2_Lemonade"
+
+ Application.put_env(:explorer, :http_adapter, HTTPoison)
+ end
+
+ test "replace {id} with actual token_id", %{bypass: bypass} do
+ json = """
+ {
+ "name": "Sérgio Mendonça {id}"
+ }
+ """
+
+ abi =
+ [
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [],
+ "name" => "tokenURI",
+ "inputs" => [
+ %{"type" => "string", "name" => "name", "internalType" => "string"}
+ ]
+ }
+ ]
+ |> ABI.parse_specification()
+ |> Enum.at(0)
+
+ encoded_url =
+ abi
+ |> Encoder.encode_function_call(["http://localhost:#{bypass.port}/api/card/{id}"])
+ |> String.replace("4cf12d26", "")
+
+ EthereumJSONRPC.Mox
+ |> expect(:json_rpc, fn [
+ %{
+ id: 0,
+ jsonrpc: "2.0",
+ method: "eth_call",
+ params: [
+ %{
+ data:
+ "0x0e89341c0000000000000000000000000000000000000000000000000000000000000309",
+ to: "0x5caebd3b32e210e85ce3e9d51638b9c445481567"
+ },
+ "latest"
+ ]
+ }
+ ],
+ _options ->
+ {:ok,
+ [
+ %{
+ id: 0,
+ jsonrpc: "2.0",
+ result: encoded_url
+ }
+ ]}
+ end)
+
+ Bypass.expect(
+ bypass,
+ "GET",
+ "/api/card/0000000000000000000000000000000000000000000000000000000000000309",
+ fn conn ->
+ Conn.resp(conn, 200, json)
+ end
+ )
+
+ insert(:token,
+ contract_address: build(:address, hash: "0x5caebd3b32e210e85ce3e9d51638b9c445481567"),
+ type: "ERC-1155"
+ )
+
+ assert [
+ {:ok,
+ %Instance{
+ metadata: %{
+ "name" => "Sérgio Mendonça 0000000000000000000000000000000000000000000000000000000000000309"
+ }
+ }}
+ ] = Helper.batch_fetch_instances([{"0x5caebd3b32e210e85ce3e9d51638b9c445481567", 777}])
+ end
+
+ test "fetch ipfs of ipfs/{id} format" do
+ EthereumJSONRPC.Mox
+ |> expect(:json_rpc, fn [
+ %{
+ id: 0,
+ jsonrpc: "2.0",
+ method: "eth_call",
+ params: [
+ %{
+ data:
+ "0xc87b56dd0000000000000000000000000000000000000000000000000000000000000000",
+ to: "0x7e01CC81fCfdf6a71323900288A69e234C464f63"
+ },
+ "latest"
+ ]
+ }
+ ],
+ _options ->
+ {:ok,
+ [
+ %{
+ id: 0,
+ jsonrpc: "2.0",
+ result:
+ "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000033697066732f516d6439707654684577676a544262456b4e6d6d47466263704a4b773137666e524241543454643472636f67323200000000000000000000000000"
+ }
+ ]}
+ end)
+
+ Application.put_env(:explorer, :http_adapter, Explorer.Mox.HTTPoison)
+
+ Explorer.Mox.HTTPoison
+ |> expect(:get, fn "https://ipfs.io/ipfs/Qmd9pvThEwgjTBbEkNmmGFbcpJKw17fnRBAT4Td4rcog22", _headers, _options ->
+ {:ok, %HTTPoison.Response{status_code: 200, body: "123", headers: [{"Content-Type", "image/jpg"}]}}
+ end)
+
+ insert(:token,
+ contract_address: build(:address, hash: "0x7e01CC81fCfdf6a71323900288A69e234C464f63"),
+ type: "ERC-721"
+ )
+
+ assert [
+ {:ok,
+ %Instance{
+ metadata: %{
+ "image" => "https://ipfs.io/ipfs/Qmd9pvThEwgjTBbEkNmmGFbcpJKw17fnRBAT4Td4rcog22"
+ }
+ }}
+ ] = Helper.batch_fetch_instances([{"0x7e01CC81fCfdf6a71323900288A69e234C464f63", 0}])
+
+ Application.put_env(:explorer, :http_adapter, HTTPoison)
+ end
+ end
+end
diff --git a/apps/indexer/test/indexer/fetcher/withdrawal_test.exs b/apps/indexer/test/indexer/fetcher/withdrawal_test.exs
index cfe60e9e4afb..f3b13f783af8 100644
--- a/apps/indexer/test/indexer/fetcher/withdrawal_test.exs
+++ b/apps/indexer/test/indexer/fetcher/withdrawal_test.exs
@@ -5,7 +5,6 @@ defmodule Indexer.Fetcher.WithdrawalTest do
import Mox
import EthereumJSONRPC, only: [integer_to_quantity: 1]
- alias Explorer.Chain
alias Indexer.Fetcher.Withdrawal
setup :verify_on_exit!
diff --git a/apps/indexer/test/indexer/transform/address_coin_balances_test.exs b/apps/indexer/test/indexer/transform/address_coin_balances_test.exs
index 6a6627dda70e..ae2889b07b95 100644
--- a/apps/indexer/test/indexer/transform/address_coin_balances_test.exs
+++ b/apps/indexer/test/indexer/transform/address_coin_balances_test.exs
@@ -110,6 +110,7 @@ defmodule Indexer.Transform.AddressCoinBalancesTest do
|> Factory.params_for()
|> Map.put(:block_number, block_number)
|> Map.put(:address_hash, address_hash)
+ |> Map.put(:first_topic, nil)
params_set = AddressCoinBalances.params_set(%{logs_params: [log_params]})
@@ -129,6 +130,7 @@ defmodule Indexer.Transform.AddressCoinBalancesTest do
|> Factory.params_for()
|> Map.put(:block_number, nil)
|> Map.put(:address_hash, address_hash)
+ |> Map.put(:first_topic, nil)
|> Map.put(:type, "pending")
log_params2 =
@@ -136,6 +138,7 @@ defmodule Indexer.Transform.AddressCoinBalancesTest do
|> Factory.params_for()
|> Map.put(:block_number, block_number)
|> Map.put(:address_hash, address_hash)
+ |> Map.put(:first_topic, nil)
params_set = AddressCoinBalances.params_set(%{logs_params: [log_params1, log_params2]})
diff --git a/apps/indexer/test/indexer/transform/address_token_balances_test.exs b/apps/indexer/test/indexer/transform/address_token_balances_test.exs
index 04111ff26215..b70e2f84fe09 100644
--- a/apps/indexer/test/indexer/transform/address_token_balances_test.exs
+++ b/apps/indexer/test/indexer/transform/address_token_balances_test.exs
@@ -66,7 +66,7 @@ defmodule Indexer.Transform.AddressTokenBalancesTest do
])
end
- test "does not set params when the to_address_hash is the burn address for the Token ERC-721" do
+ test "does set params when the to_address_hash is the burn address for the Token ERC-721" do
block_number = 1
from_address_hash = "0x5b8410f67eb8040bb1cd1e8a4ff9d5f6ce678a15"
to_address_hash = "0x0000000000000000000000000000000000000000"
@@ -77,12 +77,22 @@ defmodule Indexer.Transform.AddressTokenBalancesTest do
from_address_hash: from_address_hash,
to_address_hash: to_address_hash,
token_contract_address_hash: token_contract_address_hash,
- token_type: "ERC-721"
+ token_type: "ERC-721",
+ token_ids: [1]
}
params_set = AddressTokenBalances.params_set(%{token_transfers_params: [token_transfer_params]})
- assert MapSet.size(params_set) == 0
+ assert params_set ==
+ MapSet.new([
+ %{
+ address_hash: "0x5b8410f67eb8040bb1cd1e8a4ff9d5f6ce678a15",
+ block_number: 1,
+ token_contract_address_hash: "0xe18035bf8712672935fdb4e5e431b1a0183d2dfc",
+ token_id: 1,
+ token_type: "ERC-721"
+ }
+ ])
end
end
end
diff --git a/apps/indexer/test/indexer/transform/mint_transfers_test.exs b/apps/indexer/test/indexer/transform/mint_transfers_test.exs
index 04499a7af454..4670e983a272 100644
--- a/apps/indexer/test/indexer/transform/mint_transfers_test.exs
+++ b/apps/indexer/test/indexer/transform/mint_transfers_test.exs
@@ -17,8 +17,7 @@ defmodule Indexer.Transform.MintTransfersTest do
index: 1,
second_topic: "0x0000000000000000000000009a4a90e2732f3fa4087b0bb4bf85c76d14833df1",
third_topic: "0x0000000000000000000000007301cfa0e1756b71869e93d4e4dca5c7d0eb0aa6",
- transaction_hash: "0x1d5066d30ff3404a9306733136103ac2b0b989951c38df637f464f3667f8d4ee",
- type: "mined"
+ transaction_hash: "0x1d5066d30ff3404a9306733136103ac2b0b989951c38df637f464f3667f8d4ee"
}
]
@@ -47,8 +46,7 @@ defmodule Indexer.Transform.MintTransfersTest do
index: 1,
second_topic: "0x0000000000000000000000009a4a90e2732f3fa4087b0bb4bf85c76d14833df1",
third_topic: "0x0000000000000000000000007301cfa0e1756b71869e93d4e4dca5c7d0eb0aa6",
- transaction_hash: "0x1d5066d30ff3404a9306733136103ac2b0b989951c38df637f464f3667f8d4ee",
- type: "mined"
+ transaction_hash: "0x1d5066d30ff3404a9306733136103ac2b0b989951c38df637f464f3667f8d4ee"
}
]
diff --git a/apps/indexer/test/indexer/transform/token_transfers_test.exs b/apps/indexer/test/indexer/transform/token_transfers_test.exs
index 731c27752063..0c670035cc35 100644
--- a/apps/indexer/test/indexer/transform/token_transfers_test.exs
+++ b/apps/indexer/test/indexer/transform/token_transfers_test.exs
@@ -7,7 +7,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
describe "parse/1" do
test "parse/1 parses logs for tokens and token transfers" do
- [log_1, _log_2, log_3] =
+ [log_1, _log_2, log_3, weth_deposit_log, weth_withdrawal_log] =
logs = [
%{
address_hash: "0xf2eec76e45b328df99a34fa696320a262cb92154",
@@ -19,8 +19,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
index: 8,
second_topic: "0x000000000000000000000000556813d9cc20acfe8388af029a679d34a63388db",
third_topic: "0x00000000000000000000000092148dd870fa1b7c4700f2bd7f44238821c26f73",
- transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5",
- type: "mined"
+ transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5"
},
%{
address_hash: "0x6ea5ec9cb832e60b6b1654f5826e9be638f276a5",
@@ -32,8 +31,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
index: 0,
second_topic: "0x00000000000000000000000063b0595bb7a0b7edd0549c9557a0c8aee6da667b",
third_topic: "0x000000000000000000000000f3089e15d0c23c181d7f98b0878b560bfe193a1d",
- transaction_hash: "0x8425a9b81a9bd1c64861110c1a453b84719cb0361d6fa0db68abf7611b9a890e",
- type: "mined"
+ transaction_hash: "0x8425a9b81a9bd1c64861110c1a453b84719cb0361d6fa0db68abf7611b9a890e"
},
%{
address_hash: "0x91932e8c6776fb2b04abb71874a7988747728bb2",
@@ -45,8 +43,31 @@ defmodule Indexer.Transform.TokenTransfersTest do
index: 1,
second_topic: "0x0000000000000000000000009851ba177554eb07271ac230a137551e6dd0aa84",
third_topic: "0x000000000000000000000000dccb72afee70e60b0c1226288fe86c01b953e8ac",
- transaction_hash: "0x4011d9a930a3da620321589a54dc0ca3b88216b4886c7a7c3aaad1fb17702d35",
- type: "mined"
+ transaction_hash: "0x4011d9a930a3da620321589a54dc0ca3b88216b4886c7a7c3aaad1fb17702d35"
+ },
+ %{
+ address_hash: "0x0BE9e53fd7EDaC9F859882AfdDa116645287C629",
+ block_number: 23_704_638,
+ block_hash: "0x8f61c99b0dd1196714ffda5bf979a282e6a62fdd3cff25c291284e6b57de2106",
+ data: "0x00000000000000000000000000000000000000000000002be19edfcf6b480000",
+ first_topic: "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c",
+ second_topic: "0x000000000000000000000000fb76e9e7d88e308ab530330ed90e84a952570319",
+ third_topic: nil,
+ fourth_topic: nil,
+ index: 1,
+ transaction_hash: "0x185889bc91372106ecf114a4e23f4ee615e131ae3e698078bd5d2ed7e3f55a49"
+ },
+ %{
+ address_hash: "0x0BE9e53fd7EDaC9F859882AfdDa116645287C629",
+ block_number: 23_704_608,
+ block_hash: "0x5a5e69984f78d65fc6d92e18058d21a9b114f1d56d06ca7aa017b3d87bf0491a",
+ data: "0x00000000000000000000000000000000000000000000000000e1315e1ebd28e8",
+ first_topic: "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65",
+ second_topic: "0x000000000000000000000000e3f85aad0c8dd7337427b9df5d0fb741d65eeeb5",
+ third_topic: nil,
+ fourth_topic: nil,
+ index: 1,
+ transaction_hash: "0x07510dbfddbac9064f7d607c2d9a14aa26fa19cdfcd578c0b585ff2395df543f"
}
]
@@ -59,6 +80,10 @@ defmodule Indexer.Transform.TokenTransfersTest do
%{
contract_address_hash: log_1.address_hash,
type: "ERC-20"
+ },
+ %{
+ contract_address_hash: weth_withdrawal_log.address_hash,
+ type: "ERC-20"
}
],
token_transfers: [
@@ -84,6 +109,30 @@ defmodule Indexer.Transform.TokenTransfersTest do
transaction_hash: log_1.transaction_hash,
token_type: "ERC-20",
block_hash: log_1.block_hash
+ },
+ %{
+ amount: Decimal.new("63386150072297704"),
+ block_hash: weth_withdrawal_log.block_hash,
+ block_number: weth_withdrawal_log.block_number,
+ from_address_hash: truncated_hash(weth_withdrawal_log.second_topic),
+ log_index: 1,
+ to_address_hash: "0x0000000000000000000000000000000000000000",
+ token_contract_address_hash: weth_withdrawal_log.address_hash,
+ token_ids: nil,
+ token_type: "ERC-20",
+ transaction_hash: weth_withdrawal_log.transaction_hash
+ },
+ %{
+ amount: Decimal.new("809467672956315893760"),
+ block_hash: weth_deposit_log.block_hash,
+ block_number: weth_deposit_log.block_number,
+ from_address_hash: "0x0000000000000000000000000000000000000000",
+ log_index: 1,
+ to_address_hash: truncated_hash(weth_deposit_log.second_topic),
+ token_contract_address_hash: weth_deposit_log.address_hash,
+ token_ids: nil,
+ token_type: "ERC-20",
+ transaction_hash: weth_deposit_log.transaction_hash
}
]
}
@@ -103,8 +152,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
second_topic: nil,
third_topic: nil,
transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8",
- block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
- type: "mined"
+ block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca"
}
expected = %{
@@ -144,8 +192,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
fourth_topic: "0x0000000000000000000000009c978f4cfa1fe13406bcc05baf26a35716f881dd",
index: 2,
transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8",
- block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
- type: "mined"
+ block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca"
}
assert TokenTransfers.parse([log]) == %{
@@ -186,8 +233,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
fourth_topic: "0x0000000000000000000000006c943470780461b00783ad530a53913bd2c104d3",
index: 2,
transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8",
- block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
- type: "mined"
+ block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca"
}
assert TokenTransfers.parse([log]) == %{
@@ -209,6 +255,27 @@ defmodule Indexer.Transform.TokenTransfersTest do
}
end
+ test "parses erc1155 batch token transfer with empty ids/values" do
+ log = %{
+ address_hash: "0x598AF04C88122FA4D1e08C5da3244C39F10D4F14",
+ block_number: 9_065_059,
+ data:
+ "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ first_topic: "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb",
+ secon_topic: "0x81D0caF80E9bFfD9bF9c641ab964feB9ef69069e",
+ third_topic: "0x598AF04C88122FA4D1e08C5da3244C39F10D4F14",
+ fourth_topic: "0x0000000000000000000000000000000000000000",
+ index: 6,
+ transaction_hash: "0xa6ad6588edb4abd8ca45f30d2f026ba20b68a3002a5870dbd30cc3752568483b",
+ block_hash: "0x61b720e40f8c521edd77a52cabce556c18b18b198f78e361f310003386ff1f02"
+ }
+
+ assert TokenTransfers.parse([log]) == %{
+ token_transfers: [],
+ tokens: []
+ }
+ end
+
test "logs error with unrecognized token transfer format" do
log = %{
address_hash: "0x58Ab73CB79c8275628E0213742a85B163fE0A9Fb",
@@ -220,8 +287,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
index: 2,
second_topic: nil,
third_topic: nil,
- transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8",
- type: "mined"
+ transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8"
}
error = capture_log(fn -> %{tokens: [], token_transfers: []} = TokenTransfers.parse([log]) end)
@@ -243,8 +309,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
index: 8,
second_topic: "0x000000000000000000000000556813d9cc20acfe8388af029a679d34a63388db",
third_topic: "0x00000000000000000000000092148dd870fa1b7c4700f2bd7f44238821c26f73",
- transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5",
- type: "mined"
+ transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5"
}
assert %{
@@ -267,8 +332,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
index: 8,
second_topic: "0x000000000000000000000000556813d9cc20acfe8388af029a679d34a63388db",
third_topic: "0x00000000000000000000000092148dd870fa1b7c4700f2bd7f44238821c26f73",
- transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5",
- type: "mined"
+ transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5"
},
%{
address_hash: contract_address_hash,
@@ -281,8 +345,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
fourth_topic: "0x0000000000000000000000009c978f4cfa1fe13406bcc05baf26a35716f881dd",
index: 2,
transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5",
- block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
- type: "mined"
+ block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca"
}
]
diff --git a/apps/indexer/test/support/indexer/fetcher/block_reward_supervisor_case.ex b/apps/indexer/test/support/indexer/fetcher/block_reward_supervisor_case.ex
index 95ac8836072f..f6678cc715c7 100644
--- a/apps/indexer/test/support/indexer/fetcher/block_reward_supervisor_case.ex
+++ b/apps/indexer/test/support/indexer/fetcher/block_reward_supervisor_case.ex
@@ -7,7 +7,8 @@ defmodule Indexer.Fetcher.BlockReward.Supervisor.Case do
fetcher_arguments,
flush_interval: 50,
max_batch_size: 1,
- max_concurrency: 1
+ max_concurrency: 1,
+ poll: false
)
[merged_fetcher_arguments]
diff --git a/apps/indexer/test/support/indexer/fetcher/internal_transaction_supervisor_case.ex b/apps/indexer/test/support/indexer/fetcher/internal_transaction_supervisor_case.ex
index b067997b5fbf..0c3ba2be7358 100644
--- a/apps/indexer/test/support/indexer/fetcher/internal_transaction_supervisor_case.ex
+++ b/apps/indexer/test/support/indexer/fetcher/internal_transaction_supervisor_case.ex
@@ -4,11 +4,13 @@ defmodule Indexer.Fetcher.InternalTransaction.Supervisor.Case do
def start_supervised!(fetcher_arguments \\ []) when is_list(fetcher_arguments) do
merged_fetcher_arguments =
Keyword.merge(
- fetcher_arguments,
- flush_interval: 50,
- max_batch_size: 1,
- max_concurrency: 1,
- poll: false
+ [
+ flush_interval: 50,
+ max_batch_size: 1,
+ max_concurrency: 1,
+ poll: false
+ ],
+ fetcher_arguments
)
[merged_fetcher_arguments]
diff --git a/apps/indexer/test/support/indexer/fetcher/rootstock_data_supervisor_case.ex b/apps/indexer/test/support/indexer/fetcher/rootstock_data_supervisor_case.ex
new file mode 100644
index 000000000000..d58f3c5f10cd
--- /dev/null
+++ b/apps/indexer/test/support/indexer/fetcher/rootstock_data_supervisor_case.ex
@@ -0,0 +1,17 @@
+defmodule Indexer.Fetcher.RootstockData.Supervisor.Case do
+ alias Indexer.Fetcher.RootstockData
+
+ def start_supervised!(fetcher_arguments \\ []) when is_list(fetcher_arguments) do
+ merged_fetcher_arguments =
+ Keyword.merge(
+ fetcher_arguments,
+ interval: 1,
+ batch_size: 1,
+ max_concurrency: 1
+ )
+
+ [merged_fetcher_arguments]
+ |> RootstockData.Supervisor.child_spec()
+ |> ExUnit.Callbacks.start_supervised!()
+ end
+end
diff --git a/apps/indexer/test/test_helper.exs b/apps/indexer/test/test_helper.exs
index 0df43337026a..381b6272bc29 100644
--- a/apps/indexer/test/test_helper.exs
+++ b/apps/indexer/test/test_helper.exs
@@ -17,6 +17,7 @@ end
Mox.defmock(EthereumJSONRPC.Mox, for: EthereumJSONRPC.Transport)
Mox.defmock(Indexer.BufferedTaskTest.RetryableTask, for: Indexer.BufferedTask)
Mox.defmock(Indexer.BufferedTaskTest.ShrinkableTask, for: Indexer.BufferedTask)
+Mox.defmock(Explorer.Mox.HTTPoison, for: HTTPoison.Base)
ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter])
ExUnit.start()
diff --git a/bin/deploy b/bin/deploy
index f346f52f99e0..acb9750618ca 100755
--- a/bin/deploy
+++ b/bin/deploy
@@ -120,5 +120,5 @@ for chain in $CHAINS; do
i=$((i+1))
done
-log "Deployment task has succesfully completed!"
+log "Deployment task has successfully completed!"
exit 0
diff --git a/bin/install_chrome_headless.sh b/bin/install_chrome_headless.sh
index 1b365ed7a861..63f2d98a2493 100755
--- a/bin/install_chrome_headless.sh
+++ b/bin/install_chrome_headless.sh
@@ -1,11 +1,12 @@
export DISPLAY=:99.0
sh -e /etc/init.d/xvfb start
-# export CHROMEDRIVER_VERSION=`curl -s http://chromedriver.storage.googleapis.com/LATEST_RELEASE`
-export CHROMEDRIVER_VERSION=`104.0.5112.79`
-curl -L -O "http://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip"
-unzip chromedriver_linux64.zip
+
+export CHROMEDRIVER_VERSION=$(curl -s "https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions.json" | jq -r '.channels' | jq -r '.Stable' | jq -r '.version')
+curl -L -O "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/${CHROMEDRIVER_VERSION}/linux64/chromedriver-linux64.zip"
+unzip -j chromedriver-linux64.zip
sudo chmod +x chromedriver
sudo mv chromedriver /usr/local/bin
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo apt install ./google-chrome-stable_current_amd64.deb
+sudo apt-get update
sudo apt-get install libstdc++6
diff --git a/config/config.exs b/config/config.exs
index 554d3ae557b1..f09d21a3f065 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -38,9 +38,17 @@ config :logger,
{LoggerFileBackend, :api},
{LoggerFileBackend, :block_import_timings},
{LoggerFileBackend, :account},
- {LoggerFileBackend, :api_v2}
+ {LoggerFileBackend, :api_v2},
+ LoggerJSON
]
+config :logger_json, :backend,
+ metadata:
+ ~w(application fetcher request_id first_block_number last_block_number missing_block_range_count missing_block_count
+ block_number step count error_count shrunk import_id transaction_id duration status unit endpoint method)a,
+ json_encoder: Jason,
+ formatter: LoggerJSON.Formatters.BasicLogger
+
config :logger, :console,
# Use same format for all loggers, even though the level should only ever be `:error` for `:error` backend
format: "$dateT$time $metadata[$level] $message\n",
diff --git a/config/config_helper.exs b/config/config_helper.exs
index 9d51608a2698..d5f843bc3c70 100644
--- a/config/config_helper.exs
+++ b/config/config_helper.exs
@@ -1,8 +1,21 @@
defmodule ConfigHelper do
import Bitwise
alias Explorer.ExchangeRates.Source
+ alias Explorer.Market.History.Source.{MarketCap, Price, TVL}
alias Indexer.Transform.Blocks
+ def repos do
+ base_repos = [Explorer.Repo, Explorer.Repo.Account]
+
+ case System.get_env("CHAIN_TYPE") do
+ "polygon_edge" -> base_repos ++ [Explorer.Repo.PolygonEdge]
+ "polygon_zkevm" -> base_repos ++ [Explorer.Repo.PolygonZkevm]
+ "rsk" -> base_repos ++ [Explorer.Repo.RSK]
+ "suave" -> base_repos ++ [Explorer.Repo.Suave]
+ _ -> base_repos
+ end
+ end
+
@spec hackney_options() :: any()
def hackney_options() do
basic_auth_user = System.get_env("ETHEREUM_JSONRPC_USER", "")
@@ -25,7 +38,7 @@ defmodule ConfigHelper do
|> :timer.seconds()
end
- @spec parse_integer_env_var(String.t(), String.t()) :: non_neg_integer()
+ @spec parse_integer_env_var(String.t(), integer()) :: non_neg_integer()
def parse_integer_env_var(env_var, default_value) do
env_var
|> safe_get_env(to_string(default_value))
@@ -36,6 +49,17 @@ defmodule ConfigHelper do
end
end
+ @spec parse_integer_or_nil_env_var(String.t()) :: non_neg_integer() | nil
+ def parse_integer_or_nil_env_var(env_var) do
+ env_var
+ |> System.get_env("")
+ |> Integer.parse()
+ |> case do
+ {integer, _} -> integer
+ _ -> nil
+ end
+ end
+
@spec parse_time_env_var(String.t(), String.t() | nil) :: non_neg_integer()
def parse_time_env_var(env_var, default_value) do
case env_var |> safe_get_env(default_value) |> String.downcase() |> Integer.parse() do
@@ -88,10 +112,37 @@ defmodule ConfigHelper do
@spec exchange_rates_source() :: Source.CoinGecko | Source.CoinMarketCap
def exchange_rates_source do
- cond do
- System.get_env("EXCHANGE_RATES_SOURCE") == "coin_gecko" -> Source.CoinGecko
- System.get_env("EXCHANGE_RATES_SOURCE") == "coin_market_cap" -> Source.CoinMarketCap
- true -> Source.CoinGecko
+ case System.get_env("EXCHANGE_RATES_MARKET_CAP_SOURCE") do
+ "coin_gecko" -> Source.CoinGecko
+ "coin_market_cap" -> Source.CoinMarketCap
+ _ -> Source.CoinGecko
+ end
+ end
+
+ @spec exchange_rates_market_cap_source() :: MarketCap.CoinGecko | MarketCap.CoinMarketCap
+ def exchange_rates_market_cap_source do
+ case System.get_env("EXCHANGE_RATES_MARKET_CAP_SOURCE") do
+ "coin_gecko" -> MarketCap.CoinGecko
+ "coin_market_cap" -> MarketCap.CoinMarketCap
+ _ -> MarketCap.CoinGecko
+ end
+ end
+
+ @spec exchange_rates_tvl_source() :: TVL.DefiLlama
+ def exchange_rates_tvl_source do
+ case System.get_env("EXCHANGE_RATES_TVL_SOURCE") do
+ "defillama" -> TVL.DefiLlama
+ _ -> TVL.DefiLlama
+ end
+ end
+
+ @spec exchange_rates_price_source() :: Price.CoinGecko | Price.CoinMarketCap | Price.CryptoCompare
+ def exchange_rates_price_source do
+ case System.get_env("EXCHANGE_RATES_PRICE_SOURCE") do
+ "coin_gecko" -> Price.CoinGecko
+ "coin_market_cap" -> Price.CoinMarketCap
+ "crypto_compare" -> Price.CryptoCompare
+ _ -> Price.CryptoCompare
end
end
@@ -119,4 +170,21 @@ defmodule ConfigHelper do
transformer
end
end
+
+ @spec parse_json_env_var(String.t(), String.t()) :: any()
+ def parse_json_env_var(env_var, default_value) do
+ env_var
+ |> safe_get_env(default_value)
+ |> Jason.decode!()
+ rescue
+ err -> raise "Invalid JSON in environment variable #{env_var}: #{inspect(err)}"
+ end
+
+ @spec chain_type() :: String.t()
+ def chain_type, do: System.get_env("CHAIN_TYPE") || "ethereum"
+
+ @spec eth_call_url(String.t() | nil) :: String.t() | nil
+ def eth_call_url(default \\ nil) do
+ System.get_env("ETHEREUM_JSONRPC_ETH_CALL_URL") || System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || default
+ end
end
diff --git a/config/dev.exs b/config/dev.exs
index 323212e107f7..27154824a939 100644
--- a/config/dev.exs
+++ b/config/dev.exs
@@ -3,6 +3,8 @@ import Config
# DO NOT make it `:debug` or all Ecto logs will be shown for indexer
config :logger, :console, level: :info
+config :logger_json, :backend, level: :none
+
config :logger, :ecto,
level: :debug,
path: Path.absname("logs/dev/ecto.log")
diff --git a/config/prod.exs b/config/prod.exs
index 1d9be54e3fc6..2e8c9db24d97 100644
--- a/config/prod.exs
+++ b/config/prod.exs
@@ -2,7 +2,9 @@ import Config
# Do not print debug messages in production
-config :logger, :console, level: :info
+config :logger, :console, level: :none
+
+config :logger_json, :backend, level: :info
config :logger, :ecto,
level: :info,
diff --git a/config/runtime.exs b/config/runtime.exs
index 744a8f21d7d6..c81ceeafbbc4 100644
--- a/config/runtime.exs
+++ b/config/runtime.exs
@@ -130,7 +130,11 @@ config :block_scout_web, :chart,
config :block_scout_web, BlockScoutWeb.Chain.Address.CoinBalance,
coin_balance_history_days: ConfigHelper.parse_integer_env_var("COIN_BALANCE_HISTORY_DAYS", 10)
-config :block_scout_web, BlockScoutWeb.API.V2, enabled: ConfigHelper.parse_bool_env_var("API_V2_ENABLED")
+config :block_scout_web, BlockScoutWeb.API.V2, enabled: ConfigHelper.parse_bool_env_var("API_V2_ENABLED", "true")
+
+config :block_scout_web, BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation,
+ service_url: System.get_env("MICROSERVICE_TRANSACTION_INTERPRETATION_URL"),
+ enabled: ConfigHelper.parse_bool_env_var("MICROSERVICE_TRANSACTION_INTERPRETATION_ENABLED")
# Configures Ueberauth's Auth0 auth provider
config :ueberauth, Ueberauth.Strategy.Auth0.OAuth,
@@ -148,21 +152,37 @@ config :ueberauth, Ueberauth, logout_url: "https://#{System.get_env("ACCOUNT_AUT
config :ethereum_jsonrpc,
rpc_transport: if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http", do: :http, else: :ipc),
ipc_path: System.get_env("IPC_PATH"),
- disable_archive_balances?: ConfigHelper.parse_bool_env_var("ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES")
+ disable_archive_balances?: ConfigHelper.parse_bool_env_var("ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES"),
+ archive_balances_window: ConfigHelper.parse_integer_env_var("ETHEREUM_JSONRPC_ARCHIVE_BALANCES_WINDOW", 200)
+
+config :ethereum_jsonrpc, EthereumJSONRPC.HTTP,
+ headers:
+ %{"Content-Type" => "application/json"}
+ |> Map.merge(ConfigHelper.parse_json_env_var("ETHEREUM_JSONRPC_HTTP_HEADERS", "{}"))
+ |> Map.to_list()
config :ethereum_jsonrpc, EthereumJSONRPC.Geth,
+ block_traceable?: ConfigHelper.parse_bool_env_var("ETHEREUM_JSONRPC_GETH_TRACE_BY_BLOCK"),
debug_trace_transaction_timeout: System.get_env("ETHEREUM_JSONRPC_DEBUG_TRACE_TRANSACTION_TIMEOUT", "5s"),
- tracer: System.get_env("INDEXER_INTERNAL_TRANSACTIONS_TRACER_TYPE", "call_tracer")
+ tracer:
+ if(ConfigHelper.chain_type() == "polygon_edge",
+ do: "polygon_edge",
+ else: System.get_env("INDEXER_INTERNAL_TRANSACTIONS_TRACER_TYPE", "call_tracer")
+ )
config :ethereum_jsonrpc, EthereumJSONRPC.PendingTransaction,
type: System.get_env("ETHEREUM_JSONRPC_PENDING_TRANSACTIONS_TYPE", "default")
+config :ethereum_jsonrpc, EthereumJSONRPC.RequestCoordinator,
+ wait_per_timeout: ConfigHelper.parse_time_env_var("ETHEREUM_JSONRPC_WAIT_PER_TIMEOUT", "20s")
+
################
### Explorer ###
################
disable_indexer? = ConfigHelper.parse_bool_env_var("DISABLE_INDEXER")
disable_webapp? = ConfigHelper.parse_bool_env_var("DISABLE_WEBAPP")
+disable_exchange_rates? = ConfigHelper.parse_bool_env_var("DISABLE_EXCHANGE_RATES")
checksum_function = System.get_env("CHECKSUM_FUNCTION")
exchange_rates_coin = System.get_env("EXCHANGE_RATES_COIN")
@@ -170,9 +190,12 @@ exchange_rates_coin = System.get_env("EXCHANGE_RATES_COIN")
config :explorer,
coin: System.get_env("COIN") || exchange_rates_coin || "ETH",
coin_name: System.get_env("COIN_NAME") || exchange_rates_coin || "ETH",
- allowed_evm_versions:
- System.get_env("CONTRACT_VERIFICATION_ALLOWED_EVM_VERSIONS") ||
- "homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,berlin,london,paris,default",
+ allowed_solidity_evm_versions:
+ System.get_env("CONTRACT_VERIFICATION_ALLOWED_SOLIDITY_EVM_VERSIONS") ||
+ "homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,berlin,london,paris,shanghai,default",
+ allowed_vyper_evm_versions:
+ System.get_env("CONTRACT_VERIFICATION_ALLOWED_VYPER_EVM_VERSIONS") ||
+ "byzantium,constantinople,petersburg,istanbul,berlin,paris,shanghai,default",
include_uncles_in_average_block_time: ConfigHelper.parse_bool_env_var("UNCLES_IN_AVERAGE_BLOCK_TIME"),
healthy_blocks_period: ConfigHelper.parse_time_env_var("HEALTHY_BLOCKS_PERIOD", "5m"),
realtime_events_sender:
@@ -180,15 +203,18 @@ config :explorer,
do: Explorer.Chain.Events.DBSender,
else: Explorer.Chain.Events.SimpleSender
),
- enable_caching_implementation_data_of_proxy: true,
- avg_block_time_as_ttl_cached_implementation_data_of_proxy: true,
- fallback_ttl_cached_implementation_data_of_proxy: :timer.seconds(4),
- implementation_data_fetching_timeout: :timer.seconds(2),
restricted_list: System.get_env("RESTRICTED_LIST"),
restricted_list_key: System.get_env("RESTRICTED_LIST_KEY"),
checksum_function: checksum_function && String.to_atom(checksum_function),
elasticity_multiplier: ConfigHelper.parse_integer_env_var("EIP_1559_ELASTICITY_MULTIPLIER", 2)
+config :explorer, :proxy,
+ caching_implementation_data_enabled: true,
+ implementation_data_ttl_via_avg_block_time:
+ ConfigHelper.parse_bool_env_var("CONTRACT_PROXY_IMPLEMENTATION_TTL_VIA_AVG_BLOCK_TIME", "true"),
+ fallback_cached_implementation_data_ttl: :timer.seconds(4),
+ implementation_data_fetching_timeout: :timer.seconds(2)
+
config :explorer, Explorer.Chain.Events.Listener,
enabled:
if(disable_webapp? && disable_indexer?,
@@ -216,13 +242,22 @@ config :explorer, Explorer.Chain.Cache.Block,
config :explorer, Explorer.Chain.Cache.Transaction,
global_ttl: ConfigHelper.parse_time_env_var("CACHE_TXS_COUNT_PERIOD", "2h")
+config :explorer, Explorer.Chain.Cache.PendingBlockOperation,
+ global_ttl: ConfigHelper.parse_time_env_var("CACHE_PBO_COUNT_PERIOD", "20m")
+
config :explorer, Explorer.Chain.Cache.GasPriceOracle,
global_ttl: ConfigHelper.parse_time_env_var("GAS_PRICE_ORACLE_CACHE_PERIOD", "30s"),
+ simple_transaction_gas: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_SIMPLE_TRANSACTION_GAS", 21000),
num_of_blocks: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_NUM_OF_BLOCKS", 200),
safelow_percentile: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_SAFELOW_PERCENTILE", 35),
average_percentile: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_AVERAGE_PERCENTILE", 60),
fast_percentile: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_FAST_PERCENTILE", 90)
+config :explorer, Explorer.Chain.Cache.RootstockLockedBTC,
+ enabled: System.get_env("ETHEREUM_JSONRPC_VARIANT") == "rsk",
+ global_ttl: ConfigHelper.parse_time_env_var("ROOTSTOCK_LOCKED_BTC_CACHE_PERIOD", "10m"),
+ locking_cap: ConfigHelper.parse_integer_env_var("ROOTSTOCK_LOCKING_CAP", 21_000_000)
+
config :explorer, Explorer.Counters.AddressTransactionsGasUsageCounter,
cache_period: ConfigHelper.parse_time_env_var("CACHE_ADDRESS_TRANSACTIONS_GAS_USAGE_COUNTER_PERIOD", "30m")
@@ -251,26 +286,35 @@ config :explorer, Explorer.Counters.AddressTokenTransfersCounter,
config :explorer, Explorer.ExchangeRates,
store: :ets,
- enabled: !ConfigHelper.parse_bool_env_var("DISABLE_EXCHANGE_RATES"),
+ enabled: !disable_exchange_rates?,
fetch_btc_value: ConfigHelper.parse_bool_env_var("EXCHANGE_RATES_FETCH_BTC_VALUE")
-config :explorer, Explorer.ExchangeRates.Source, source: ConfigHelper.exchange_rates_source()
+config :explorer, Explorer.ExchangeRates.Source,
+ source: ConfigHelper.exchange_rates_source(),
+ price_source: ConfigHelper.exchange_rates_price_source(),
+ market_cap_source: ConfigHelper.exchange_rates_market_cap_source(),
+ tvl_source: ConfigHelper.exchange_rates_tvl_source()
config :explorer, Explorer.ExchangeRates.Source.CoinMarketCap,
- api_key: System.get_env("EXCHANGE_RATES_COINMARKETCAP_API_KEY")
+ api_key: System.get_env("EXCHANGE_RATES_COINMARKETCAP_API_KEY"),
+ coin_id: System.get_env("EXCHANGE_RATES_COINMARKETCAP_COIN_ID")
config :explorer, Explorer.ExchangeRates.Source.CoinGecko,
platform: System.get_env("EXCHANGE_RATES_COINGECKO_PLATFORM_ID"),
api_key: System.get_env("EXCHANGE_RATES_COINGECKO_API_KEY"),
coin_id: System.get_env("EXCHANGE_RATES_COINGECKO_COIN_ID")
+config :explorer, Explorer.ExchangeRates.Source.DefiLlama, coin_id: System.get_env("EXCHANGE_RATES_DEFILLAMA_COIN_ID")
+
config :explorer, Explorer.ExchangeRates.TokenExchangeRates,
enabled: !ConfigHelper.parse_bool_env_var("DISABLE_TOKEN_EXCHANGE_RATE", "true"),
interval: ConfigHelper.parse_time_env_var("TOKEN_EXCHANGE_RATE_INTERVAL", "5s"),
refetch_interval: ConfigHelper.parse_time_env_var("TOKEN_EXCHANGE_RATE_REFETCH_INTERVAL", "1h"),
max_batch_size: ConfigHelper.parse_integer_env_var("TOKEN_EXCHANGE_RATE_MAX_BATCH_SIZE", 150)
-config :explorer, Explorer.Market.History.Cataloger, enabled: !disable_indexer?
+config :explorer, Explorer.Market.History.Cataloger, enabled: !disable_indexer? && !disable_exchange_rates?
+
+config :explorer, Explorer.Chain.Transaction, suave_bid_contracts: System.get_env("SUAVE_BID_CONTRACTS", "")
config :explorer, Explorer.Chain.Transaction.History.Historian,
enabled: ConfigHelper.parse_bool_env_var("TXS_STATS_ENABLED", "true"),
@@ -323,17 +367,35 @@ config :explorer, Explorer.Chain.Cache.Uncles,
ttl_check_interval: ConfigHelper.cache_ttl_check_interval(disable_indexer?),
global_ttl: ConfigHelper.cache_global_ttl(disable_indexer?)
+config :explorer, Explorer.Chain.Cache.Uncles,
+ ttl_check_interval: ConfigHelper.cache_ttl_check_interval(disable_indexer?),
+ global_ttl: ConfigHelper.cache_global_ttl(disable_indexer?)
+
config :explorer, Explorer.ThirdPartyIntegrations.Sourcify,
server_url: System.get_env("SOURCIFY_SERVER_URL") || "https://sourcify.dev/server",
enabled: ConfigHelper.parse_bool_env_var("SOURCIFY_INTEGRATION_ENABLED"),
chain_id: System.get_env("CHAIN_ID"),
repo_url: System.get_env("SOURCIFY_REPO_URL") || "https://repo.sourcify.dev/contracts"
+config :explorer, Explorer.ThirdPartyIntegrations.SolidityScan,
+ chain_id: System.get_env("SOLIDITYSCAN_CHAIN_ID"),
+ api_key: System.get_env("SOLIDITYSCAN_API_TOKEN")
+
+config :explorer, Explorer.ThirdPartyIntegrations.NovesFi,
+ service_url: System.get_env("NOVES_FI_BASE_API_URL") || "https://blockscout.noves.fi",
+ chain_name: System.get_env("NOVES_FI_CHAIN_NAME"),
+ api_key: System.get_env("NOVES_FI_API_TOKEN")
+
+enabled? = ConfigHelper.parse_bool_env_var("MICROSERVICE_SC_VERIFIER_ENABLED")
+# or "eth_bytecode_db"
+type = System.get_env("MICROSERVICE_SC_VERIFIER_TYPE", "sc_verifier")
+
config :explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour,
- service_url: System.get_env("MICROSERVICE_SC_VERIFIER_URL"),
- enabled: ConfigHelper.parse_bool_env_var("MICROSERVICE_SC_VERIFIER_ENABLED"),
- # or "eth_bytecode_db"
- type: System.get_env("MICROSERVICE_SC_VERIFIER_TYPE", "sc_verifier")
+ service_url: System.get_env("MICROSERVICE_SC_VERIFIER_URL") || "https://eth-bytecode-db.services.blockscout.com/",
+ enabled: enabled?,
+ type: type,
+ eth_bytecode_db?: enabled? && type == "eth_bytecode_db",
+ api_key: System.get_env("MICROSERVICE_SC_VERIFIER_API_KEY")
config :explorer, Explorer.Visualize.Sol2uml,
service_url: System.get_env("MICROSERVICE_VISUALIZE_SOL2UML_URL"),
@@ -357,7 +419,11 @@ config :explorer, Explorer.Account,
sender: System.get_env("ACCOUNT_SENDGRID_SENDER"),
template: System.get_env("ACCOUNT_SENDGRID_TEMPLATE")
],
- resend_interval: ConfigHelper.parse_time_env_var("ACCOUNT_VERIFICATION_EMAIL_RESEND_INTERVAL", "5m")
+ resend_interval: ConfigHelper.parse_time_env_var("ACCOUNT_VERIFICATION_EMAIL_RESEND_INTERVAL", "5m"),
+ private_tags_limit: ConfigHelper.parse_integer_env_var("ACCOUNT_PRIVATE_TAGS_LIMIT", 2000),
+ watchlist_addresses_limit: ConfigHelper.parse_integer_env_var("ACCOUNT_WATCHLIST_ADDRESSES_LIMIT", 15),
+ notifications_limit_for_30_days:
+ ConfigHelper.parse_integer_env_var("ACCOUNT_WATCHLIST_NOTIFICATIONS_LIMIT_FOR_30_DAYS", 1000)
config :explorer, :token_id_migration,
first_block: ConfigHelper.parse_integer_env_var("TOKEN_ID_MIGRATION_FIRST_BLOCK", 0),
@@ -377,29 +443,65 @@ config :explorer, Explorer.Chain.Cache.TransactionActionTokensData,
max_cache_size: ConfigHelper.parse_integer_env_var("INDEXER_TX_ACTIONS_MAX_TOKEN_CACHE_SIZE", 100_000)
config :explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand,
- fetch_interval:
- ConfigHelper.parse_time_env_var("MICROSERVICE_MICROSERVICE_ETH_BYTECODE_DB_INTERVAL_BETWEEN_LOOKUPS", "10m")
+ fetch_interval: ConfigHelper.parse_time_env_var("MICROSERVICE_ETH_BYTECODE_DB_INTERVAL_BETWEEN_LOOKUPS", "10m"),
+ max_concurrency: ConfigHelper.parse_integer_env_var("MICROSERVICE_ETH_BYTECODE_DB_MAX_LOOKUPS_CONCURRENCY", 10)
config :explorer, Explorer.Chain.Cache.MinMissingBlockNumber,
enabled: !ConfigHelper.parse_bool_env_var("DISABLE_INDEXER")
+config :explorer, Explorer.TokenInstanceOwnerAddressMigration,
+ concurrency: ConfigHelper.parse_integer_env_var("TOKEN_INSTANCE_OWNER_MIGRATION_CONCURRENCY", 5),
+ batch_size: ConfigHelper.parse_integer_env_var("TOKEN_INSTANCE_OWNER_MIGRATION_BATCH_SIZE", 50),
+ enabled: ConfigHelper.parse_bool_env_var("TOKEN_INSTANCE_OWNER_MIGRATION_ENABLED")
+
+config :explorer, Explorer.Chain.Transaction,
+ rootstock_remasc_address: System.get_env("ROOTSTOCK_REMASC_ADDRESS"),
+ rootstock_bridge_address: System.get_env("ROOTSTOCK_BRIDGE_ADDRESS")
+
+config :explorer, Explorer.Chain.Cache.AddressesTabsCounters,
+ ttl: ConfigHelper.parse_time_env_var("ADDRESSES_TABS_COUNTERS_TTL", "10m")
+
+config :explorer, Explorer.MicroserviceInterfaces.BENS,
+ service_url: System.get_env("MICROSERVICE_BENS_URL"),
+ enabled: ConfigHelper.parse_bool_env_var("MICROSERVICE_BENS_ENABLED")
+
+config :explorer, Explorer.Migrator.TransactionsDenormalization,
+ batch_size: ConfigHelper.parse_integer_env_var("DENORMALIZATION_MIGRATION_BATCH_SIZE", 500),
+ concurrency: ConfigHelper.parse_integer_env_var("DENORMALIZATION_MIGRATION_CONCURRENCY", 10)
+
###############
### Indexer ###
###############
+trace_first_block = ConfigHelper.parse_integer_env_var("TRACE_FIRST_BLOCK", 0)
+trace_last_block = ConfigHelper.parse_integer_or_nil_env_var("TRACE_LAST_BLOCK")
+
+trace_block_ranges =
+ case ConfigHelper.safe_get_env("TRACE_BLOCK_RANGES", nil) do
+ "" -> "#{trace_first_block}..#{trace_last_block || "latest"}"
+ ranges -> ranges
+ end
+
config :indexer,
block_transformer: ConfigHelper.block_transformer(),
metadata_updater_milliseconds_interval: ConfigHelper.parse_time_env_var("TOKEN_METADATA_UPDATE_INTERVAL", "48h"),
block_ranges: System.get_env("BLOCK_RANGES"),
- first_block: System.get_env("FIRST_BLOCK") || "",
- last_block: System.get_env("LAST_BLOCK") || "",
- trace_first_block: System.get_env("TRACE_FIRST_BLOCK") || "",
- trace_last_block: System.get_env("TRACE_LAST_BLOCK") || "",
+ first_block: ConfigHelper.parse_integer_env_var("FIRST_BLOCK", 0),
+ last_block: ConfigHelper.parse_integer_or_nil_env_var("LAST_BLOCK"),
+ trace_block_ranges: trace_block_ranges,
+ trace_first_block: trace_first_block,
+ trace_last_block: trace_last_block,
fetch_rewards_way: System.get_env("FETCH_REWARDS_WAY", "trace_block"),
memory_limit: ConfigHelper.indexer_memory_limit(),
receipts_batch_size: ConfigHelper.parse_integer_env_var("INDEXER_RECEIPTS_BATCH_SIZE", 250),
receipts_concurrency: ConfigHelper.parse_integer_env_var("INDEXER_RECEIPTS_CONCURRENCY", 10),
- hide_indexing_progress_alert: ConfigHelper.parse_bool_env_var("INDEXER_HIDE_INDEXING_PROGRESS_ALERT")
+ hide_indexing_progress_alert: ConfigHelper.parse_bool_env_var("INDEXER_HIDE_INDEXING_PROGRESS_ALERT"),
+ fetcher_init_limit: ConfigHelper.parse_integer_env_var("INDEXER_FETCHER_INIT_QUERY_LIMIT", 100),
+ token_balances_fetcher_init_limit:
+ ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_BALANCES_FETCHER_INIT_QUERY_LIMIT", 100_000),
+ coin_balances_fetcher_init_limit:
+ ConfigHelper.parse_integer_env_var("INDEXER_COIN_BALANCES_FETCHER_INIT_QUERY_LIMIT", 2000),
+ ipfs_gateway_url: System.get_env("IPFS_GATEWAY_URL", "https://ipfs.io/ipfs")
config :indexer, Indexer.Supervisor, enabled: !ConfigHelper.parse_bool_env_var("DISABLE_INDEXER")
@@ -427,8 +529,11 @@ config :indexer, Indexer.Fetcher.PendingTransaction.Supervisor,
System.get_env("ETHEREUM_JSONRPC_VARIANT") == "besu" ||
ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER")
+config :indexer, Indexer.Fetcher.Token, concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_CONCURRENCY", 10)
+
config :indexer, Indexer.Fetcher.TokenBalance,
- batch_size: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_BALANCES_BATCH_SIZE", 100)
+ batch_size: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_BALANCES_BATCH_SIZE", 100),
+ concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_BALANCES_CONCURRENCY", 10)
config :indexer, Indexer.Fetcher.TokenBalanceOnDemand,
threshold: ConfigHelper.parse_time_env_var("TOKEN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD", "1h"),
@@ -456,6 +561,8 @@ config :indexer, Indexer.Fetcher.EmptyBlocksSanitizer.Supervisor,
config :indexer, Indexer.Block.Realtime.Supervisor,
enabled: !ConfigHelper.parse_bool_env_var("DISABLE_REALTIME_INDEXER")
+config :indexer, Indexer.Block.Catchup.Supervisor, enabled: !ConfigHelper.parse_bool_env_var("DISABLE_CATCHUP_INDEXER")
+
config :indexer, Indexer.Fetcher.TokenInstance.Realtime.Supervisor,
disabled?: ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_TOKEN_INSTANCE_REALTIME_FETCHER")
@@ -465,8 +572,12 @@ config :indexer, Indexer.Fetcher.TokenInstance.Retry.Supervisor,
config :indexer, Indexer.Fetcher.TokenInstance.Sanitize.Supervisor,
disabled?: ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_TOKEN_INSTANCE_SANITIZE_FETCHER")
+config :indexer, Indexer.Fetcher.TokenInstance.LegacySanitize.Supervisor,
+ disabled?: ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_TOKEN_INSTANCE_LEGACY_SANITIZE_FETCHER", "true")
+
config :indexer, Indexer.Fetcher.EmptyBlocksSanitizer,
- batch_size: ConfigHelper.parse_integer_env_var("INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE", 100)
+ batch_size: ConfigHelper.parse_integer_env_var("INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE", 100),
+ interval: ConfigHelper.parse_time_env_var("INDEXER_EMPTY_BLOCKS_SANITIZER_INTERVAL", "10s")
config :indexer, Indexer.Block.Realtime.Fetcher,
max_gap: ConfigHelper.parse_integer_env_var("INDEXER_REALTIME_FETCHER_MAX_GAP", 1000)
@@ -484,17 +595,26 @@ config :indexer, Indexer.Fetcher.BlockReward,
config :indexer, Indexer.Fetcher.TokenInstance.Retry,
concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_RETRY_CONCURRENCY", 10),
+ batch_size: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_RETRY_BATCH_SIZE", 10),
refetch_interval: ConfigHelper.parse_time_env_var("INDEXER_TOKEN_INSTANCE_RETRY_REFETCH_INTERVAL", "24h")
config :indexer, Indexer.Fetcher.TokenInstance.Realtime,
- concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_REALTIME_CONCURRENCY", 10)
+ concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_REALTIME_CONCURRENCY", 10),
+ batch_size: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_REALTIME_BATCH_SIZE", 1)
config :indexer, Indexer.Fetcher.TokenInstance.Sanitize,
- concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_SANITIZE_CONCURRENCY", 10)
+ concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_SANITIZE_CONCURRENCY", 10),
+ batch_size: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_SANITIZE_BATCH_SIZE", 10)
+
+config :indexer, Indexer.Fetcher.TokenInstance.LegacySanitize,
+ concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_LEGACY_SANITIZE_CONCURRENCY", 2),
+ batch_size: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_LEGACY_SANITIZE_BATCH_SIZE", 10)
config :indexer, Indexer.Fetcher.InternalTransaction,
batch_size: ConfigHelper.parse_integer_env_var("INDEXER_INTERNAL_TRANSACTIONS_BATCH_SIZE", 10),
- concurrency: ConfigHelper.parse_integer_env_var("INDEXER_INTERNAL_TRANSACTIONS_CONCURRENCY", 4)
+ concurrency: ConfigHelper.parse_integer_env_var("INDEXER_INTERNAL_TRANSACTIONS_CONCURRENCY", 4),
+ indexing_finished_threshold:
+ ConfigHelper.parse_integer_env_var("INDEXER_INTERNAL_TRANSACTIONS_INDEXING_FINISHED_THRESHOLD", 1000)
config :indexer, Indexer.Fetcher.CoinBalance,
batch_size: ConfigHelper.parse_integer_env_var("INDEXER_COIN_BALANCES_BATCH_SIZE", 500),
@@ -505,6 +625,60 @@ config :indexer, Indexer.Fetcher.Withdrawal.Supervisor,
config :indexer, Indexer.Fetcher.Withdrawal, first_block: System.get_env("WITHDRAWALS_FIRST_BLOCK")
+config :indexer, Indexer.Fetcher.PolygonEdge.Supervisor, disabled?: !(ConfigHelper.chain_type() == "polygon_edge")
+
+config :indexer, Indexer.Fetcher.PolygonEdge.Deposit.Supervisor,
+ disabled?: !(ConfigHelper.chain_type() == "polygon_edge")
+
+config :indexer, Indexer.Fetcher.PolygonEdge.DepositExecute.Supervisor,
+ disabled?: !(ConfigHelper.chain_type() == "polygon_edge")
+
+config :indexer, Indexer.Fetcher.PolygonEdge.Withdrawal.Supervisor,
+ disabled?: !(ConfigHelper.chain_type() == "polygon_edge")
+
+config :indexer, Indexer.Fetcher.PolygonEdge.WithdrawalExit.Supervisor,
+ disabled?: !(ConfigHelper.chain_type() == "polygon_edge")
+
+config :indexer, Indexer.Fetcher.PolygonEdge,
+ polygon_edge_l1_rpc: System.get_env("INDEXER_POLYGON_EDGE_L1_RPC"),
+ polygon_edge_eth_get_logs_range_size:
+ ConfigHelper.parse_integer_env_var("INDEXER_POLYGON_EDGE_ETH_GET_LOGS_RANGE_SIZE", 1000)
+
+config :indexer, Indexer.Fetcher.PolygonEdge.Deposit,
+ start_block_l1: System.get_env("INDEXER_POLYGON_EDGE_L1_DEPOSITS_START_BLOCK"),
+ state_sender: System.get_env("INDEXER_POLYGON_EDGE_L1_STATE_SENDER_CONTRACT")
+
+config :indexer, Indexer.Fetcher.PolygonEdge.DepositExecute,
+ start_block_l2: System.get_env("INDEXER_POLYGON_EDGE_L2_DEPOSITS_START_BLOCK"),
+ state_receiver: System.get_env("INDEXER_POLYGON_EDGE_L2_STATE_RECEIVER_CONTRACT")
+
+config :indexer, Indexer.Fetcher.PolygonEdge.Withdrawal,
+ start_block_l2: System.get_env("INDEXER_POLYGON_EDGE_L2_WITHDRAWALS_START_BLOCK"),
+ state_sender: System.get_env("INDEXER_POLYGON_EDGE_L2_STATE_SENDER_CONTRACT")
+
+config :indexer, Indexer.Fetcher.PolygonEdge.WithdrawalExit,
+ start_block_l1: System.get_env("INDEXER_POLYGON_EDGE_L1_WITHDRAWALS_START_BLOCK"),
+ exit_helper: System.get_env("INDEXER_POLYGON_EDGE_L1_EXIT_HELPER_CONTRACT")
+
+config :indexer, Indexer.Fetcher.Zkevm.TransactionBatch,
+ chunk_size: ConfigHelper.parse_integer_env_var("INDEXER_ZKEVM_BATCHES_CHUNK_SIZE", 20),
+ recheck_interval: ConfigHelper.parse_integer_env_var("INDEXER_ZKEVM_BATCHES_RECHECK_INTERVAL", 60)
+
+config :indexer, Indexer.Fetcher.Zkevm.TransactionBatch.Supervisor,
+ enabled:
+ System.get_env("CHAIN_TYPE", "ethereum") == "polygon_zkevm" &&
+ ConfigHelper.parse_bool_env_var("INDEXER_ZKEVM_BATCHES_ENABLED")
+
+config :indexer, Indexer.Fetcher.RootstockData.Supervisor,
+ disabled?:
+ ConfigHelper.chain_type() != "rsk" || ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_ROOTSTOCK_DATA_FETCHER")
+
+config :indexer, Indexer.Fetcher.RootstockData,
+ interval: ConfigHelper.parse_time_env_var("INDEXER_ROOTSTOCK_DATA_FETCHER_INTERVAL", "3s"),
+ batch_size: ConfigHelper.parse_integer_env_var("INDEXER_ROOTSTOCK_DATA_FETCHER_BATCH_SIZE", 10),
+ max_concurrency: ConfigHelper.parse_integer_env_var("INDEXER_ROOTSTOCK_DATA_FETCHER_CONCURRENCY", 5),
+ db_batch_size: ConfigHelper.parse_integer_env_var("INDEXER_ROOTSTOCK_DATA_FETCHER_DB_BATCH_SIZE", 300)
+
Code.require_file("#{config_env()}.exs", "config/runtime")
for config <- "../apps/*/config/runtime/#{config_env()}.exs" |> Path.expand(__DIR__) |> Path.wildcard() do
diff --git a/config/runtime/dev.exs b/config/runtime/dev.exs
index 853a30bd1836..3e4df28fb71e 100644
--- a/config/runtime/dev.exs
+++ b/config/runtime/dev.exs
@@ -7,25 +7,20 @@ alias Explorer.Repo.ConfigHelper, as: ExplorerConfigHelper
### BlockScout Web ###
######################
-port =
- case System.get_env("PORT") && Integer.parse(System.get_env("PORT")) do
- {port, _} -> port
- :error -> nil
- nil -> nil
- end
+port = ExplorerConfigHelper.get_port()
config :block_scout_web, BlockScoutWeb.Endpoint,
secret_key_base:
System.get_env("SECRET_KEY_BASE") || "RMgI4C1HSkxsEjdhtGMfwAHfyT6CKWXOgzCboJflfSm4jeAlic52io05KB6mqzc5",
http: [
- port: port || 4000
+ port: port
],
url: [
scheme: "http",
host: System.get_env("BLOCKSCOUT_HOST", "localhost")
],
https: [
- port: (port && port + 1) || 4001,
+ port: port + 1,
cipher_suite: :strong,
certfile: System.get_env("CERTFILE") || "priv/cert/selfsigned.pem",
keyfile: System.get_env("KEYFILE") || "priv/cert/selfsigned_key.pem"
@@ -47,12 +42,15 @@ pool_size =
do: ConfigHelper.parse_integer_env_var("POOL_SIZE", 30),
else: ConfigHelper.parse_integer_env_var("POOL_SIZE", 40)
+queue_target = ConfigHelper.parse_integer_env_var("DATABASE_QUEUE_TARGET", 50)
+
# Configure your database
config :explorer, Explorer.Repo,
database: database,
hostname: hostname,
url: System.get_env("DATABASE_URL"),
- pool_size: pool_size
+ pool_size: pool_size,
+ queue_target: queue_target
database_api = if System.get_env("DATABASE_READ_ONLY_API_URL"), do: nil, else: database
hostname_api = if System.get_env("DATABASE_READ_ONLY_API_URL"), do: nil, else: hostname
@@ -62,7 +60,8 @@ config :explorer, Explorer.Repo.Replica1,
database: database_api,
hostname: hostname_api,
url: ExplorerConfigHelper.get_api_db_url(),
- pool_size: ConfigHelper.parse_integer_env_var("POOL_SIZE_API", 10)
+ pool_size: ConfigHelper.parse_integer_env_var("POOL_SIZE_API", 10),
+ queue_target: queue_target
database_account = if System.get_env("ACCOUNT_DATABASE_URL"), do: nil, else: database
hostname_account = if System.get_env("ACCOUNT_DATABASE_URL"), do: nil, else: hostname
@@ -72,7 +71,42 @@ config :explorer, Explorer.Repo.Account,
database: database_account,
hostname: hostname_account,
url: ExplorerConfigHelper.get_account_db_url(),
- pool_size: ConfigHelper.parse_integer_env_var("ACCOUNT_POOL_SIZE", 10)
+ pool_size: ConfigHelper.parse_integer_env_var("ACCOUNT_POOL_SIZE", 10),
+ queue_target: queue_target
+
+# Configure PolygonEdge database
+config :explorer, Explorer.Repo.PolygonEdge,
+ database: database,
+ hostname: hostname,
+ url: System.get_env("DATABASE_URL"),
+ # actually this repo is not started, and its pool size remains unused.
+ # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type
+ pool_size: 1
+
+# Configure PolygonZkevm database
+config :explorer, Explorer.Repo.PolygonZkevm,
+ database: database,
+ hostname: hostname,
+ url: System.get_env("DATABASE_URL"),
+ # actually this repo is not started, and its pool size remains unused.
+ # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type
+ pool_size: 1
+
+# Configure Rootstock database
+config :explorer, Explorer.Repo.RSK,
+ database: database,
+ hostname: hostname,
+ url: System.get_env("DATABASE_URL"),
+ # actually this repo is not started, and its pool size remains unused.
+ # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type
+ pool_size: 1
+
+# Configure Suave database
+config :explorer, Explorer.Repo.Suave,
+ database: database,
+ hostname: hostname,
+ url: ExplorerConfigHelper.get_suave_db_url(),
+ pool_size: 1
variant = Variant.get()
diff --git a/config/runtime/prod.exs b/config/runtime/prod.exs
index 640f34ed0be0..ce4602522a1d 100644
--- a/config/runtime/prod.exs
+++ b/config/runtime/prod.exs
@@ -7,13 +7,15 @@ alias Explorer.Repo.ConfigHelper, as: ExplorerConfigHelper
### BlockScout Web ###
######################
+port = ExplorerConfigHelper.get_port()
+
config :block_scout_web, BlockScoutWeb.Endpoint,
secret_key_base: System.get_env("SECRET_KEY_BASE"),
check_origin: System.get_env("CHECK_ORIGIN", "false") == "true" || false,
- http: [port: System.get_env("PORT")],
+ http: [port: port],
url: [
scheme: System.get_env("BLOCKSCOUT_PROTOCOL") || "https",
- port: System.get_env("PORT"),
+ port: port,
host: System.get_env("BLOCKSCOUT_HOST") || "localhost"
]
@@ -25,32 +27,58 @@ config :block_scout_web, BlockScoutWeb.Endpoint,
### Explorer ###
################
-pool_size =
- if System.get_env("DATABASE_READ_ONLY_API_URL"),
- do: ConfigHelper.parse_integer_env_var("POOL_SIZE", 50),
- else: ConfigHelper.parse_integer_env_var("POOL_SIZE", 40)
+pool_size = ConfigHelper.parse_integer_env_var("POOL_SIZE", 50)
+queue_target = ConfigHelper.parse_integer_env_var("DATABASE_QUEUE_TARGET", 50)
# Configures the database
config :explorer, Explorer.Repo,
url: System.get_env("DATABASE_URL"),
pool_size: pool_size,
- ssl: ExplorerConfigHelper.ssl_enabled?()
-
-pool_size_api =
- if System.get_env("DATABASE_READ_ONLY_API_URL"),
- do: ConfigHelper.parse_integer_env_var("POOL_SIZE_API", 50),
- else: ConfigHelper.parse_integer_env_var("POOL_SIZE_API", 10)
+ ssl: ExplorerConfigHelper.ssl_enabled?(),
+ queue_target: queue_target
# Configures API the database
config :explorer, Explorer.Repo.Replica1,
url: ExplorerConfigHelper.get_api_db_url(),
- pool_size: pool_size_api,
- ssl: ExplorerConfigHelper.ssl_enabled?()
+ pool_size: ConfigHelper.parse_integer_env_var("POOL_SIZE_API", 50),
+ ssl: ExplorerConfigHelper.ssl_enabled?(),
+ queue_target: queue_target
# Configures Account database
config :explorer, Explorer.Repo.Account,
url: ExplorerConfigHelper.get_account_db_url(),
pool_size: ConfigHelper.parse_integer_env_var("ACCOUNT_POOL_SIZE", 50),
+ ssl: ExplorerConfigHelper.ssl_enabled?(),
+ queue_target: queue_target
+
+# Configures PolygonEdge database
+config :explorer, Explorer.Repo.PolygonEdge,
+ url: System.get_env("DATABASE_URL"),
+ # actually this repo is not started, and its pool size remains unused.
+ # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type
+ pool_size: 1,
+ ssl: ExplorerConfigHelper.ssl_enabled?()
+
+# Configures PolygonZkevm database
+config :explorer, Explorer.Repo.PolygonZkevm,
+ url: System.get_env("DATABASE_URL"),
+ # actually this repo is not started, and its pool size remains unused.
+ # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type
+ pool_size: 1,
+ ssl: ExplorerConfigHelper.ssl_enabled?()
+
+# Configures Rootstock database
+config :explorer, Explorer.Repo.RSK,
+ url: System.get_env("DATABASE_URL"),
+ # actually this repo is not started, and its pool size remains unused.
+ # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type
+ pool_size: 1,
+ ssl: ExplorerConfigHelper.ssl_enabled?()
+
+# Configures Suave database
+config :explorer, Explorer.Repo.Suave,
+ url: ExplorerConfigHelper.get_suave_db_url(),
+ pool_size: 1,
ssl: ExplorerConfigHelper.ssl_enabled?()
variant = Variant.get()
diff --git a/config/test.exs b/config/test.exs
index c87adaeda1de..c979cd834877 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -4,12 +4,16 @@ import Config
config :logger, :console, level: :warn
+config :logger_json, :backend, level: :none
+
config :logger, :ecto,
level: :warn,
path: Path.absname("logs/test/ecto.log")
config :logger, :error, path: Path.absname("logs/test/error.log")
-config :explorer, Explorer.ExchangeRates,
+config :explorer, Explorer.ExchangeRates, store: :none
+
+config :explorer, Explorer.ExchangeRates.Source,
source: Explorer.ExchangeRates.Source.NoOpSource,
- store: :none
+ price_source: Explorer.ExchangeRates.Source.NoOpPriceSource
diff --git a/cspell.json b/cspell.json
index 35a424da029d..aa6c88ac854f 100644
--- a/cspell.json
+++ b/cspell.json
@@ -25,6 +25,7 @@
"ARGMAX",
"arounds",
"asda",
+ "Asfpp",
"atoken",
"autodetectfalse",
"Autodetection",
@@ -117,6 +118,7 @@
"decompiler",
"Decompiler",
"dedup",
+ "DefiLlama",
"defmock",
"defsupervisor",
"dejob",
@@ -125,6 +127,8 @@
"DELEGATECALL",
"delegators",
"demonitor",
+ "denormalization",
+ "Denormalization",
"Denormalized",
"descr",
"describedby",
@@ -147,6 +151,7 @@
"Erigon",
"errora",
"errorb",
+ "erts",
"Ethash",
"etherchain",
"ethsupply",
@@ -163,6 +168,7 @@
"Faileddi",
"falala",
"Filesize",
+ "fkey",
"Floki",
"fontawesome",
"fortawesome",
@@ -230,6 +236,7 @@
"kittencream",
"labeledby",
"labelledby",
+ "lastmod",
"lastname",
"lastword",
"lformat",
@@ -254,11 +261,13 @@
"mconst",
"mdef",
"MDWW",
+ "meer",
"Mendonça",
"Menlo",
"mergeable",
"Merkle",
"metatags",
+ "microsecs",
"millis",
"mintings",
"mistmatches",
@@ -285,6 +294,7 @@
"mydep",
"nanomorph",
"nbsp",
+ "Nerg",
"Nethermind",
"Neue",
"newkey",
@@ -302,6 +312,8 @@
"noproc",
"noreferrer",
"noreply",
+ "noves",
+ "NovesFi",
"nowarn",
"nowrap",
"ntoa",
@@ -313,8 +325,11 @@
"onconnect",
"ondisconnect",
"outcoming",
+ "overengineering",
"pawesome",
"pbcopy",
+ "peeker",
+ "peekers",
"pendingtxlist",
"perc",
"persistable",
@@ -341,16 +356,19 @@
"prederive",
"prederived",
"progressbar",
+ "proxiable",
"psql",
"purrstige",
"qdai",
"Qebz",
+ "qitmeer",
"Qmbgk",
"qrcode",
"queriable",
"questiona",
"questionb",
"qwertyufhgkhiop",
+ "qwertyuioiuytrewertyuioiuytrertyuio",
"racecar",
"raisedbrow",
"rangeright",
@@ -366,18 +384,21 @@
"REINDEX",
"relname",
"reltuples",
+ "remasc",
"removedfile",
"repayer",
"reqs",
"rerequest",
"reshows",
"retryable",
+ "returnaddress",
"reuseaddr",
"RPC's",
"RPCs",
"safelow",
"savechives",
"Secon",
+ "secp",
"Segoe",
"seindexed",
"selfdestruct",
@@ -394,6 +415,7 @@
"snapshotted",
"snapshotting",
"Sokol",
+ "SOLIDITYSCAN",
"soljson",
"someout",
"sourcecode",
@@ -420,6 +442,7 @@
"subtraces",
"successa",
"successb",
+ "supernet",
"swal",
"sweetalert",
"Synthereum",
@@ -463,6 +486,7 @@
"uncatalog",
"unclosable",
"unfetched",
+ "unfinalized",
"Unitarion",
"Unitorius",
"Unitorus",
@@ -479,7 +503,9 @@
"upserting",
"upserts",
"urijs",
+ "urlset",
"Utqn",
+ "UUPS",
"valign",
"valuemax",
"valuemin",
@@ -516,9 +542,7 @@
"zindex",
"zipcode",
"zkbob",
- "erts",
- "Asfpp",
- "Nerg"
+ "zkevm"
],
"enableFiletypes": [
"dotenv",
diff --git a/deploy/testing/eth-goerli/.sops.yaml b/deploy/testing/eth-goerli/.sops.yaml
deleted file mode 100644
index ca70dd5af411..000000000000
--- a/deploy/testing/eth-goerli/.sops.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-
----
-creation_rules:
- - path_regex: ^(.+/)?secrets\.yaml$
- pgp: >-
- 99E83B7490B1A9F51781E6055317CE0D5CE1230B
diff --git a/deploy/testing/eth-goerli/secrets.yaml b/deploy/testing/eth-goerli/secrets.yaml
deleted file mode 100644
index 05f1a27ff179..000000000000
--- a/deploy/testing/eth-goerli/secrets.yaml
+++ /dev/null
@@ -1,158 +0,0 @@
-blockscout:
- ingress:
- host:
- _default: ENC[AES256_GCM,data:vqpMhSKV74ITG50wZ/pjfqQSDb4QpNCqX4kTmhb52u63dZZVFuPSlHQ6K11LSA==,iv:BNJhX9oX7Bdz+ZsdqxoQi5Qg4V5KTm9etGAzUBtVEAI=,tag:772dB//F331xZ+WICgbZ4A==,type:str]
- environment:
- ETHEREUM_JSONRPC_TRACE_URL:
- _default: ENC[AES256_GCM,data:Gxei79q5mOdbCQXv7k/9QNpw8lrJJ2C3kQ==,iv:jgAtB+YY0xp7GbbzUkqKLMVhnCBpfOYop5oxgstTCQw=,tag:yY21Yi7Ie7S0f3KHa7pSIw==,type:str]
- ETHEREUM_JSONRPC_HTTP_URL:
- _default: ENC[AES256_GCM,data:MLJ3HsljmSFFbeKrW/+3HJoSLxu0tE7izw==,iv:vpK/M+Zdx8U2IVRa+cD3UFPpsQG4CBxfNO72I9JkbyA=,tag:Pelx5iRxmoB2ZiTIj+5FNA==,type:str]
- MICROSERVICE_SC_VERIFIER_URL:
- _default: ENC[AES256_GCM,data:PcrMP4zuXRjObBj1JI+hDE6fyhMhzseFag==,iv:J6dtGXi7yGxiPqi8Hk6/64BgsaMuU445woZnQUrZLRA=,tag:3Mejc4O3xAhsWruW+ocdbg==,type:str]
- MICROSERVICE_VISUALIZE_SOL2UML_URL:
- _default: ENC[AES256_GCM,data:MR7hsQ3ut8aPGW7O6L6Ghs8Bielnaqx+,iv:y6cnGKRv41CTWSAi1YbHdTJ3bEIV0PObmfpuz2LUAB8=,tag:iEljIcZVuxd+U/GD1K1CUQ==,type:str]
- MICROSERVICE_SIG_PROVIDER_URL:
- _default: ENC[AES256_GCM,data:RLmbcciVU8fMGQWUa2GKXjmPFCTdmYHJTrNg,iv:Hu3BiKiiK1mGLEaKeiR5v9kq5doed9UKpQXdiq7J3wY=,tag:O6Ra/oe6EW8BTff0HfLJ6w==,type:str]
- BLOCKSCOUT_HOST:
- _default: ENC[AES256_GCM,data:beCEVQcGu7YJ3FHGqYJDD65hMdboDTSpnsrXgwm6cIRihukdvcdVmzsYnjOuGg==,iv:U/ZrjGQmMhnP/oLbSZ7xMJlZNl+5XxI8qjbPJthB7Kk=,tag:r9MKtOT7cT5WDT+dQ/gW8w==,type:str]
- JSON_RPC:
- _default: ENC[AES256_GCM,data:MIGBVQ/zNmnHLuVJHDQmOZfvn5IEJKcSwRE/XAVdNg==,iv:n/XDYMMuIYvPIDd8E0srRVdOmsAnZxsvb0HmoCxBAUk=,tag:JuF1Dtd1G3O0dM3hwu7sZw==,type:str]
- GRAPHIQL_TRANSACTION:
- _default: ENC[AES256_GCM,data:ZXaGdfqEhGbPSgKEJEd/5C+n2kScv0lnvFaqbBdIp73/XbHEN28ROl6phqybrMc9kEoghLOh5WjwD3S/hHybIz2B,iv:0+GGcyGyj2q0dev2Ra4/HD+6xY5/x0KJOfBwe2hf4vk=,tag:ElGSQnegLDJNTuHNxtsrxw==,type:str]
- SUPPORTED_CHAINS:
- _default: ENC[AES256_GCM,data:0MB1opTgRIWOdHsAy+ZYSUnqi1jG2K6IFvCRsJza5oY6ElptvmU7prPqclmBB9keG8y6gA3clhcnLvy/nDcDEManI+S5EUgMr3U7g+1mY+VpJWRWNx398MINmk4bwHZD8ZKEaaqnXuDdRwryVZyqbj7lITj5NCBj9PVnQnv/Kaxz2cj6MpyofZpiVzEheA7Q6BL8jw/Vd8s7dxQg7bJBD/s8tTnaAqoK181iPK80zQrEn5LlMAm4DQBMGb4rmSfVjfryJu6ihzTba7GLrBossY/Nm53URylvNmG6m3RXSaLKtu8pN6NAxP2W/IjQ11erEte4mxFe1mcXxs8kNxD6fOtrtNcmq49EcSJSwrlAHp4wY07UWxTgmCeiD8N/gZONeX6FCaA+jZjzdrNYSfdGPDYlBnXqG4+jH0WHJuaFtyFfgvvadQRfIT4HyfcuJb7Ew0lQ/iytldnWfSBfCNt8pe5W2uEJ4Tak6dKMrrb3Kw1GMClKwL1oYTRoG6RVGltYwU3+B+4V4eD+lTHFxAeDdJf2fNHLqWqiiDCYvliKHv71ZzaQabvoIzMjk5jXS5qaqyuUnkr192FxJXySjdp2cNXKsafHdJnrG2n7CFBIt+etybXhYLD3/Lrcyjc8nHOEIsRhw50t+UZyxMyInBjaFSVlu2EghHkDg3r3lKZ5xJil6jdQxmvMgiH5qkQnwhzUKECEWTZiNx1jrhA0QUQ1ujCMZ2uyxJJBAW76qgnAgbsyaTEGIsYJk5wvZx0UTfTI+909o/kW1c5SEtESi3t/t9VaVdj325a7UuBgCWjuwpGnXKS6VMOcO6VRvY4BqTbNHm+/h5W/ZK/qft956eyO6e5KRKRPCgEmC2JA5CtFQQLYLRZwMr0r+4piI5u9rgJKF0PLYVldMA8H0rUUWNuF7DOi+F+sMGD56VH6Y39bQN7FFhVuGWb1c7cZYAXWwu+zRpPl7eCrxIcRO5zakKs8IDFFOWJmWkUmOcQZZzLqb9ROpQhucbyd8Ec9MXN2H3WDWaeX5Hz32ULGjjvgDUR3A02a9Dyv5IL7vlwCm7Fdr5uqqHJvMWN6UfV3nGOhCwWKMM8+Yvlp8BTcIN+HamwKHK06jdhZB7MmIPnZoH38AKxHCJq+1Mia3pPyb1OUp58XlK0ES7RtL6V5vsBEv4mu8CXtIfvXYHoC,iv:UvpB4xqK31vEtjAaLXAr4tyW82FSF0dRx3sHn5GzJXo=,tag:aAm88WRWD4s/8Yn/S8kzNw==,type:str]
- DATABASE_URL:
- _default: ENC[AES256_GCM,data:cB7mpaqYiuhpwie5VxWSyA0/tk5sNIdnE8Gu6+qBOBwoOXb/hixk6xDcfuz4iV3kDsJpcH/v2MnXzojDtX3o0yg=,iv:AfMtEniYnX8vJsYV7yoO66F6/BToeffxENbS0QcXIuM=,tag:mHP/8Mt1e3gM/o+5XmCz+A==,type:str]
- ACCOUNT_DATABASE_URL:
- _default: ENC[AES256_GCM,data:3T22+sjT4jklfigR2cCvwdrLLi1EmK5sMFeoLmw/Eu4mmFjZ9Ln67O5Wmw8zDrUM0KFdJuf4x7AdIbFTWcXPyZbXaWEUbBG4YQ==,iv:uodCjhbCiIZhPdO1eG2tCrxZwAyuzpcDZaerNb8CKhE=,tag:0AJ9wlcX+iycqQa3xfY1LQ==,type:str]
- SECRET_KEY_BASE:
- _default: ENC[AES256_GCM,data:PL8RKVSFqHtvoJzrAoo+F4Rdij391hUeK1FQQ+W5h4Xeq1+TJZzqD78cJufBQgvmn0/5IcDIwfg4kT+oPpqzbg==,iv:9HF7xAe8EyECkmEIIUrk8gNFfAb37m1ZX0U0/dWF7Oc=,tag:xvQIbKcEPXjKWJwG7BIIYw==,type:str]
- RE_CAPTCHA_SECRET_KEY:
- _default: ENC[AES256_GCM,data:WeroF56dG3/j7jahdwdWblQYPbQKcjffiGR8Lo5zZhA+4VZeWsLXYg==,iv:IkA2SQOJsb4T03KS+ax6NrofqifUzEKdWNQSTjeAsXM=,tag:t+PM0gLlaPRxmndDnRxNDg==,type:str]
- RE_CAPTCHA_CLIENT_KEY:
- _default: ENC[AES256_GCM,data:EmyO1sEvSxmNzSiUtl7OOy01h3FjquV16e7XGCGEdfktyBqgJ9XLzQ==,iv:mv3X1rC8kzUpaB4NHKbIEeKmuKimoA+d9e5liDou1A8=,tag:qVYjd7z6ilzrwUzQd3rYeg==,type:str]
- ACCOUNT_AUTH0_DOMAIN:
- _default: ENC[AES256_GCM,data:3twuloplIcvXb3eRcYWGC2Fg18zauwD8vxLKWjFa,iv:Cy8nMO79hXv7RGSoszNEXd4L8ZQfs6Tj0VtOtKImWgI=,tag:Y5Yv66V2pvA4NEWwkhKXGg==,type:str]
- ACCOUNT_AUTH0_CLIENT_ID:
- _default: ENC[AES256_GCM,data:d9/TFO0DcXBNy1b51vXe0y/VbUpyyWdTfoAO5/yM6pQ=,iv:J7tWz7DQEIXm5+JNxEVRjhabnaE6eHW3uze+HDgm9r8=,tag:6R9sqmkVUL2Sy6kLdFsajA==,type:str]
- ACCOUNT_AUTH0_CLIENT_SECRET:
- _default: ENC[AES256_GCM,data:+amaFFZYsjSK5CUmJuoDioStwPO/VM1NfPwVil5wLkR2sgBKUnLOd04dn9PYdvFXQ87f0OwH13NwSppKPlx3PA==,iv:fWMO6a7xtq4/OBMIo6rIHZMpuoNPbsYKiEu7lfs10XY=,tag:YXsYNKkbwT1C6tYRpni/rA==,type:str]
- ACCOUNT_SENDGRID_API_KEY:
- _default: ENC[AES256_GCM,data:wTkE2ISxbX2gpObQibxl4wH7FrKtZ2eflbVaVUsImnO6kP2XRDVaKXwqaoKkzE8hztWYOpxwMcvbF7B5tjNrWgarMvFi,iv:ngGupc1U9wOu2FKPP8QacO4NmgjH84PLyf2sjwZ0yQ8=,tag:BJr7RvrrrBZMF3OpISan8g==,type:str]
- ACCOUNT_SENDGRID_SENDER:
- _default: ENC[AES256_GCM,data:rjoADYaA1apZ0pl8V+QVISc7Vhcq4w==,iv:SwOr+jdqcs9za6fLaIzicbmDv2Wgrm6iIEqBKwT1KRI=,tag:a6/0sVVymHzseIGppbLY/w==,type:str]
- ACCOUNT_SENDGRID_TEMPLATE:
- _default: ENC[AES256_GCM,data:JrF6kgqfmvjRJAJEj153w2QobWIbLFxcL+C3j5sfbAl7sg==,iv:F2BILGqbtIHsgwibRgWgV0RFbjZygamO0iRFKZjHYFU=,tag:CR9gxOo98vuiEi+2FY3jBg==,type:str]
- ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL:
- _default: ENC[AES256_GCM,data:7PH2IDS2r7HTF8Y5fW/GvNk0ywY4ZGcu/vANV5t+J1yMj4LAuAzfb1GntnsJDgHsLvqx6fwqbDTwiOb2mwxEGQ==,iv:KscvT5H4AHsuSgk7fRp++kYaQHx/SiDk+zgqb0P0oMQ=,tag:SG1fiVGiWU/02p4pO75PIg==,type:str]
- ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY:
- _default: ENC[AES256_GCM,data:T+FkQRcBSFvY/FRtCx8XURk=,iv:bl3EjfKi8nRWlRR2zVWvBH97NsfFTtIFV5UsQU9WLr0=,tag:ODGU6tkZAzfPkaNFobEcDQ==,type:str]
- ACCOUNT_CLOAK_KEY:
- _default: ENC[AES256_GCM,data:GFYQQiw+QrMA7LaS7/sDb+YBcnbKpicGj2l4MeIoOgA0/prRMUizvwyqTQE=,iv:g580lCS6RGLUTgl4+q1g9aQQJeejX2wku6p9NbdRiVA=,tag:LNkqSkRXfxajFDAtdgnxgQ==,type:str]
- ACCOUNT_REDIS_URL:
- _default: ENC[AES256_GCM,data:31993QW0ae1+sRcNMICL6c23qE+lb5xCPEXVxMoi34i1FARER3/2FKn326qEEV9r7UWXk0a0jGAzZgSB/c0vnpTnrWWOU7sFU5qwiHUvu6Be,iv:TNHDJ9eerDzgbphPQmwhteJE1gGQbka3tEsopgN9D4I=,tag:Sx+OWiwlKZmpGpt7wYKkQw==,type:str]
-frontend:
- ingress:
- host:
- _default: ENC[AES256_GCM,data:AVrEvMPJWBhhUsDNphev65DWAr8FT3+itC/0o5oF6dj+NYmbaWfRmlWvJ8+hXA==,iv:H8+uxy6IspImxbSSx+M8QFjoZfVtp48EuoGdaCaWzJk=,tag:FYv4faD+e48U3iLP9IlFdA==,type:str]
- environment:
- NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS:
- _default: ENC[AES256_GCM,data:UpykLbd01n2vU4YSMVh41HEGIYy6o0xhqvi9PAfNK9/b3GXyjv33FYhd,iv:+XIMuaMYhlkoN+vAy93LGKLqnKZrDhKcf8R6YihOKwA=,tag:SkdEWLEmdyvVizhqFVaERA==,type:str]
- NEXT_PUBLIC_SENTRY_DSN:
- _default: ENC[AES256_GCM,data:SzsCF6Yocn9Z7rkXq75PJ7BkVqRUe2j/E9EaaJFkZuk/xKJLW2YbFrl76ZS/0iQR1Ktg7NhTe2YkD5nJ4rg7ZHKNiEpp2f34b71cG3YzKaEPbvQ=,iv:bj2cq6aRu4XRW1LGV5JJUr7dqGtXErOmVFyx4/I0vOc=,tag:1cne5902pQIc988xmYZ3zQ==,type:str]
- SENTRY_CSP_REPORT_URI:
- _default: ENC[AES256_GCM,data:0vSZJ2ZOFMC27oHVRLCf0jkniNSQU4/TVZwx3+nAGkkO/Dm2B7Rl7UzMWYAeb+UYWQxGip+gCsani02dP+o2TKDSEwZNL1+wnzVcnk68tvFFZ7iXK53jKCoXdACT7JeWarrR+VphRFxO0urk,iv:XjkF0DB3zog6iAFW4ti04Zc7CRMJorhbjZ1WcwYuNQA=,tag:X2dUjM006P/gcJ7C6C2pxQ==,type:str]
- NEXT_PUBLIC_AUTH0_CLIENT_ID:
- _default: ENC[AES256_GCM,data:0ScmS+enGat+wP4cyr1AUwESRlESyIervgRizNaZIic=,iv:JXKJXXp/Vf3BUuDCcgqXtl8/jBoyYsVk0BhKuUZuGvY=,tag:1ct04cvr8X9vl3okTQz+Ow==,type:str]
- NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID:
- _default: ENC[AES256_GCM,data:HvD7k5vjYvlYmmqOcaq7K/omYuxbAdNxZuHhX7+1dwU=,iv:KKiujbqgxXzABRUhbN8oSjU7qdWR3u1FMrjBzJfDkkM=,tag:PfeGqmvkWE3c6trwlcwnyQ==,type:str]
- NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY:
- _default: ENC[AES256_GCM,data:lWako8QyUYRzE5Czh5SK3OMVn84XzdpCf0sZR9USO6h0bvdd9DUx7w==,iv:ZNqhl1WQZhRW7BeJdaNE3Pc5JWGttcgYuRfxBR1ObRc=,tag:RVQIMKHANIv0rDe1KEgSqA==,type:str]
- NEXT_PUBLIC_FEATURED_NETWORKS:
- _default: ENC[AES256_GCM,data:jvHaezRYUJeESlOpfyPrxT6tKmC02l+0cEeFffqyE12KaOIegCbAJydWadU3rCayAykQp/YEdvtJ43CVKbuP/m14M0UXP6GeqNkZrQIbNVWy6Zepx5OGr8SoAhFWxp/vEoq3XQtOTlutGC0CXtXxRqgIdgLYmM6YW8Z5Ubic/4i8tmEhK02T0V/z95hPFLY1eU6gfGO9u3xShtgieHGixNJ0ISzSJ1YvyUHt4sr5P2h2n2n+1gORT8kAbybSxD07qRMPLuNmWlHahozQDPTmbLY7z7/Kmeul5A9nt7v7ZXrJUHMzDbD+LYF2xCY1qk+Ec1/AjqBTi1xgAKZjI0TkEs8IrUeB6LVwEs0pW8gSBoHzEC8cCZ68JkfGAi9teCvIEgqSYBwz/5uxjJTgfh3bU+Ndf0obGH9+ZWUh7979I32p9g+tuHRbQRdoFrlLcDzdKZDtZbEkbqvQpQfTPRHdOxJ24LIAYaFpfnL9AF412mGFp248G+E9vRRsrgziE9yJUQ3KUuiCKYqLo9d8ircjzORHxQx4rOiwyc2zCY5WqyxiPyAZA3Z3/q1P5J1lIxamZ7Ar+re1+B/0lRLHPPMcTCiGUAq2i7jYpTtV+5RCEph5EYTcOl04n4ZGy5DcMrLIlOVbMIV8XP2acNELs0SgovNXSchaecBEgWomWhuSciZqkDQa1PvAoUI3gxy9bZyKbEx24oz9oKY6UZKNpigTsGp2tKwRBRUpLdQeAnhGwnKJqtOwEIKtfTpqdHfHrBa7fT1NjTFlNgYWUKz9Fm+JRLF7iR6VU3iRMhHcNlRd+E5LYFocN7EqJrlGCEKM2ZRHYmyAmVKwLl4BM+hhSYs3NEyGDRlZQtjyuAHd+rGrONJhDZDNFlnzaTa0y3zjkgj8+m7ohnZ9BNWi5w5htvu3uK4CTN1LAAVpV6XlnNxYjmGZPvTg7Obn1Cubg56mqpFTYSxAWPfnr+s0YacxFPyHez8MXLaNtoBLuEMZWqPHXhUhFF/MO1r6+gWt+UsYxkKEbIqu5qBbAmoXtbGuPlkkG5ikyFpae7XMoDWtPrJ8MBq8KLfzcDy+ZLwePBss7UmRY6C2vEnuMCwVoi4U+qD1fhHUDtKyCV8BH8p7MSRKqzyX7Fmz3/1sRjESAjCenV5tj2RdRaMipOgqrXxRRh7WjS7qts+NkwwVmxqlDVgkBkiTEvK4XEF30vHedPFdkgX1A/EF8Zz8im6ma+8ALanI0Vp4NavdJo6C6530yEQDcANuo+tpyJhz+9Fm/exgQjP4wVfc8BNSHspHP7b6q4qEYl3IuiyYpsahEuZd3DbmBbsH3lBFEQtcFxRR/nY7lTKVVFkIZ/Ra47JOBuOIlMo+f9kSsRYB6xk0OeL2I0wJNGV/YMtuh0U0M4jfZ72f9QvSaeVltny26o/d7YFsXpN4vGI4htBEAaVz+oDwM7MqQrKXsKxoy8teOV71Urn/YAQZ54QF3k7iXDAN3x3cQH/dSMkcAiG6P19uQ1mPuC8g9rAeTt+aYddBKdVrWm6X19pSwFS1cAengq29ES6kz+1kmgfhEU6PkWaywXsRZlXKEILJELUu/R3nzXb7zFeYOE4rCwxPAWCyASSrT5zIstS5NCddHtkg8gM2D3h1NG47SXOLK6FbULd9Blpis3gv3uKF7FPZxFCceb8di9xOZNnlNNsJbxeZtDv9eNpW5GAuydX0WECdHI5I9xKY0zmYGCX10NfgosrOEY6M/whZAZUs2LmZZVLIjYc1ZnGfvoEA0YNdhbQ8zDolB8PMPkPHb81gGhV4ZzSSwkfXRiMrM+QG9dR/pAyjAuhggmcIpLmXRcr4espKBzPf3N43VcjBbAusV3Xlu6N244yh5kHbe29eK1IZMX7BpejnK7IB1nNKUmzKG2Xn+7eDAfQihpccHyW2SD9uoGt2NdIakqUDfbdrMUa+VgnT99u0pZExqU+fs+ItbNx6KVMllgS5hcfLuHTPYxlvQRxE1vOEkjRYwxEl5uVLDlAGwQ0nYQ5Grh1b8aEI3wZQUC0XNBbH6H+HkMdPbd0pjh6ZgTPvdqkdsuJA7OQlKP91iTcDoTYijCQWtfals2lupg03xlxra6Ll9hIG2cUQQrVglAo7UkvL01Z2MJpG6XK1iS/ZD6iGfbNQlWjAHj29+Hs4LWaVY0i9yWC5v5eKwNSM6AysIQUP75aVHYo60ZOSdipAXenSynmU6d3QW4A2PvkajtI+7T5olrA58PEtn8LH/Q2PzBc8OdE78J9Zm7FlSoJSKdcSp8UYr6/DlRjQ2v+WmQ2bFitcmXnXqZ5/hf5CEtTx9vCdBwJx8hngzTBzbNo9uhsqkAEuhikD3mZber5h80BCeOLmkLwra7B+0bAFIHaDWGIeATwVJsSaKAkXCymOD0+QULU7TdbEhxXV4cJzSYYDzN66MKbnQny9e6WVgTxsOmwPaVi9HRxmzsaZKz8vj+PtPGWPbgdUoWXV0x+W,iv:JcG3LpHO9QT9P96lIbHMcA+LVMJNk0aksoWSybuBhyg=,tag:+fR/3iHP0Cp8eY2XAEV8Ng==,type:str]
- NEXT_PUBLIC_MARKETPLACE_APP_LIST:
- _default: ENC[AES256_GCM,data:VCKlk4zVExU7alasmSsw0De3rR55COnrvPM9QjxF9ITkkriLq5wB/9Kpw+UJof98ynJxRst3vW3mI+7iz70yLBGNhw/vuUpq78nhU4SB9nq198A6ycuKrVIKBZybKU20LZOnivzs/a0TaFkpGY8UajjkpbdBa3fFUiMtkd3sdrOjFatTJ0qppJ0vTExBWxDHZf6P+sYn7l7DEMfyCSbzPmfzOYjE3r2IHfyELJ82IjqSy1SOmaIqnyhroTlj4uCpkRblwP2sAd+p4P4K86MpboNca9Gc7/Nb9My7igRwsCo2usJb9caisk9gzr27qQUfhBjT2/NPRJqLPV1vmRbUJzvFHpHh9leqPyRieUoAL7VU7OH0BQE0mhgT2LMd9dIwrZUp3bgoWLV3/i5+aWlDeRcoXUFT6O2V40uuVeZSqehoTEk0TPG/idkRoC3VsHlKiqNXuzbNnuCywWOUeozkfUu3eGCrareRifBXP6xeuZwMKcKFwa0tWivgmnr2XTgqcExV1YuLPf4OyXws9PCRo7NlUxNwZ3dYaIYadPYF+FOaXIvqFod0LxmuI92l3bvraZxvpxXJWWMmBwTW4nqrd4Z53xYMxrV7xZrx2jqt0SXcQ+sd14jp44z7jnrJOjH+MrTjUf81DKqz0WJBozvbAOU3ILOqXJkW+fbc87pAEGPeSqv2Ia8TDlh4uU5OfOLM9xtvJXg7xvPQdsfU6b0qp6hVgg8cQojhKHYXBiB1LpCLajskMYC+zarAx2VHWqvAKDwBrkVcE0jv+C9LysVKTN0nuY70/Vg99E/DXR/dpA9cnJB1vzB/m1qTomWddbslWvwydhGTgd7qdjXEB7HrjdLSFQxLUh4DTVlCeNtXc28WMc9ma0C9JndU1hmI9IMMdIcYrRrL9ac2fzGCtLAf4ML4Pnd9YzKISqwDn3fGhs6QMyedBcJ3f12en4dQbqBpZOnUIEHm1ru8DYwCd259CQNYFybh0dVJRjcnV9P3qOqHChs+F4C3C69N8ryk1bRiewlkSypb1Hc+ITq8D6pO+5QMNGEU08T/fm+0tYPAOl0lLPJhEFdYKWDKht59oV/DY1Zqef/V2dh0DMBIyVApgWjv9Gf8KzRlwViG3peFpXOXAcCZ9f1vX77tSI6Y4A9YRNXDf9dMPa4EmqAXNSQZpRMCF0ir14hIr0LVqrdmNtl3twhiVL3dEKjc2dGOPzGpzZyBVXTzPDPClRwE2maYCwRm5++9L3nBjvgQsk4+3ztsOZzd6d6526JycD4LE04O0v1KFc6b3w7kW+VINDoRuBidL39TOdgAOo8CZR0Q2XLu6w3YYhghnU1hjmIw9cCCVod9GN6T3HMCsshNc6Uh9xYCLaJM24VYHdYprPH+mEllNdWTPtlF6olksKcgXmE8E0yMsilash9xE2yCxp0kPEOqBqkxgOqumfd2jHNCRr4JfQ1/ZVEukli6NVk/ouLL10KnwL7noSioJpM3RsDSPntWCQCY/vN9wB5ZK7m01u8dZiT/HJ+HE11cmmHQ80Fmgzq6yrTQb0Qn0QyxJGNLITtoz26m0W0NIEBrzAzL2pfvNu82hZ4Tie1tGnKMDgX5wgh0Dn1Fd5zzoeYSa8oKWaJh2n4i3tT9o6d0Pl/jRwon2uIJzNN5rRg6hGZZuMWvnTAXV7mJop8T6AACZBcqq0opIb+G3mL5Ui8+Kw0VdxyOM/0T/tUL4z2ZEh/ZWZqYwTocRj7uP4AMCGH7fPeSqXeJ8A8bls6AzsPm27ivAtY8f7OLSSEu6idbTD80Htwcs9rNZ7iaxzdPTnlVduSgKS6fMVt2L2sks2//PBhvi2svMc+1jP/OL0JsGvn7FKtuI/bp+/+MrqlfuMMFlh9NCx6KXkQ0dOB0MGhDcvFYpjRZlB81JxiPNHq+1qNTDqeKsIOqCsbXqh2OnHGLU0U4hJAhNkNOLNAw9s29ddl+oBEWqihrjx2uuz/3Vl/iE7pyE/aEZJ3xRjGobUk+b8/VjYm/X7o6aSVq8wsrbgwiE1uenCLYxRBLVeX8VieTxLA+GY88eefO5NoN2IN1m+cyAVwptrISTvKqWKm1Sq9riRUft9eK298OoGN+ya3+DKgTdFM4Ym1c2cnUc+q5p4gae8SH68Ia++b2/xZ6zoBRfGZf3ZGJwP/GMDGL8QLoW90MWTG5okrM8IiI7hVEy5u79+dGPiTUAVteIrNGl8t/DJ0rcxr1xghIvqBIWJ/n79XWlik3AB78M1jKV4yDm0n3Ua27VVV1JmXGooEBzbj89KI6xK0OZ9QO62+Ed/WcKN3HW4DMO6L5cccfgx+7efh7u5/NYdGFdgKJvCKPnnYW5UEqtXxJtBWQdZ2IAABNCr59pBkgRVu6yzV0+Fqgh8De/65AkoNmKhLuiK05D8+77WHbw9AxEeVO5b7O+537OYPBH1Im7uNS/B3gaf6kev0Cqk+aeC011jlSPyuYKF/x63Bkq2cRR6qP35ncQrktUdDuoaHI+DJI/9EbXdG64qen8I5VqIeJDFCmtsCidzQL4oMDSzxULQoil8FRgXPWVlc78agakgBUmykcTaKrd8WVkbFi5SFa/OP/vQLLdiF540+iFQ3w9DyP42uh5U5OsCqR1RqdAbXHOIJJbMm0qGew1VDxZw0A1LA1nzByoYliIw+D9rS4NDiL7Fv2ieTAaNE+QrtKjJubOiy1i4LIMVmG/8OQIbrk5lPop1G1mq4i/ExSA2Z1FdyGBErQgPNMuwIhZCZUlXm+RkAjO+5feih3Vg1uU2lTByf6FRL55frbxvPAQiwnRI3xQ/eKJGxUCL3DcAklfBJC68zHbZoH9oiFNIrJUU/f3gnR+aLXQcK9OQOwVykU8MBbgXOXtQXGr70v2jCsV4YEoItOKBxGxD+N75K4ZiFeUNTb4yzkAloLUjz+LWO2CrLAXz5vMuPtpEj+Az5ZE6HYtxhstQkxH8LfVKvJwKn6zd522VIgGZ56THAYcAGRfFViLNgvbDrJEoQz4FD2Ixuko4Hoa3Y1qK4ceZXWYgTd3mOLyn3EycYmNhpZZYAxWpqY0SYNP9uSeKJuDUAzXoY5/j66AW6XJNMGvkeIiaEgtXxgdyVyQPaZIb5EpWoxrk/J0etnJjAmGhI1v7XPr2L2CCgAP/mxBKVIypyjQqavsvHxjhn9nwQbLldSc5D1CruH7GPI9tcBKqtefVxxYDgL+p8IFqPWQUxuacFivlzfyqqzJZ6kGEaZb6cdU4NiUJdLKAtszaS0Mmw+z0mcXpWMc1tEHi5I61HbIIGWZDCDoEybJpIA0SQRQSSw6RddWE7zroOVQA/FOAlfys9FlMQRVjXzTEliBnQi9gbMUPvcEWoS/4o1k6V70sScthaNkkjcoxRnoDoz9Pw1FkSxoFkDAby074wiJDQzwKaos4lWZtlcV8Vqf/g0sT5v9o04OLloiHzGjFTo/s0NrkPMB2UUjWC/vn/m3OaoW0ogSqFqGDaMt8YoO9q1LyVpM7hvEcD7BQKkPu3ouLe/hslwP9vruHpniVY2jCmEcBcljm50Bu6XbBNsdv7sS+M/JPmbRe4ApGAz1MPrBvdnAtFCE6bcuMQRQkdK6bOL5cNGGSzDRZQW33i0iTQcCrGa02kdNTDRPGP8R4o8R8loy+aMJfm98lf3x38BoGuIOWKPEqC04z4G71bmJ9r4HrDtFYPl91qDSpaFXNDatMb5biexOejU/L5+NyJfvcw5oRWcmsuwsi1a0jrdLMgatrwGQ//oHdCTwWyHNcLwk982HK7r4F5iDKJBkA/GGfx+Bo1I3SfxySbZyIeVOw7OQFL8ZUfs8L/iTY0D8hps37ELlcLC4SjwdIfWB4YXzQgBzgcSjb8GGu91qtHsLaEVRMMO46QNdZh1FB9n0+xk9M4hvmxIFONNZhp+zcXwoFekPBQIcn+IbT7ZvpFZwxtYp09DTFS1HyGDSBxTpiOISQh5BN4jGVkoZBzK29uvfAoj9kd8Ks/sw6+myVqlvdi1RgEm42bFpF/4JCTtjLrS6pv46imACUcXwbckhXK/UrdaspBociiMEvTze/g5Grb2t+g1C2RD2QgvJ1WwDCUG0Yn+0PMMXLZbTza4s2cYWjtSKLkEUkwdwaZtnhFsYmH7IBk6yRC93Gw9yi3Rv8wOoXyB9rbq+7DlObuH0oRBtC4uwcZES/43d5+lvrBY/e7wYKo8y+8oCEL2WsOMb4vVklbxF1yj1xqpjuGQyUn0WHWyLUxGNmtm7xKPL1ViQevDIvsWaz5KPTPyJBFLJ7RKnLilXVWFlRMFS/FTdi43JkYFchiiOX06vUGdx0QrwWjKGtWkCyHtLy+splSA9rPeAmWHMBkNFDlmkbUlMFOS7/DFr9pWNs5f8q3Y1S13vzynPLsB21s+xKxjyRDNVIKgJYyoZWXDEsXoqTmyj+aqLM2s5zDAT7LIQiEhrxjwBV/PPGla+Fge3qTOnm5wLC8laEyuTUG/j/iV+Ubm6+3WbWq2qje9N0s6FhUkkydgW79i3aeG+fxrd0hD2/+zc1dJfB4c9pVUYFU/3/pYZKtOv3FBJ5M7zaqTp5oyOSXkrA+2EAxBG13kxB9XMuQO7igKTr2E6Dh350H0QfIfKts0xyIjCvTekOobXnvqvIsYXDPUgWavTKjVA3vK9ZxbwRyZfQrosdOmB1dFeZzGUFW1mCHeciN3jN3yO4I5YS5wzwIpb2CsGiYOBnrRE8IU1YI7Yzhml5fSJ3dGKNCZRG+YkrzjnK+F+tmOHEEI29aFUvqicqGTU9laoNvSZJaYxA7sobFhSqd1jwoVgmAkVpwUoJdf+QbJsA5W/TOBu47SK1xCfFfrXClVoSiQISqR7HJ4edDeljlrp/XvePoa4NKmEzWVEUBOb4Cs6UXlqAoisxmCyiVYSotPbRmHZs9zjFW2iZr5lgDoB+ppzu6dVllt8hM65RHLJ7Ccoo8yjNyUm+MEywOR6/RIrBP1nVTht3JV4wnSiklcgIeL/AYIXxMSH8YVSr0VxqSVtIGFCmSuJMqU7c73fzDeUTp0W84mQFwoEOFZgsID7smydLf+FEYxuib7EO000GnrASj5dBNd+FdEWNantR74s+CuFI1bIHRyh9/f+OBS5h5I1bUKjpcfK9yG8ybAYjSWKQfL7YTIEyK6tJPYt7265zhHEb8df+Y/KQukrcge1dH6DMwWdMKxGLD6VxDFQz3mFmX2vcYfNjE4XDg28bIOaxh0eS+yuagUq7lj7XJy6NwOAyOJzjE55dGhcXON3WGSVlVuYWsGUmuiuCPGo7gtfvDC8k8jTD9qNHNlNddGHY5713IcAZVCtkzaFfwrPPf4upheqF0oKesC2RyKTO8l9+uH6r0vBzsfp3bRhFLMB2dHX67/PnPlnnMat0CN9ZvJi7RsjDekue0ufNdUl/vWlkq8vZkaKd8X9NUQAWilpb6yDn3zBoKVeasc5ds67ztJEiRY8OyhFJ/V+3NygvsM3E0nD22pHldxAwtOuJIOBd0Id73cTqjBeEgs47FqNUjmpBUy8f7WVyELa0QK1fGBbCad+TrTQkxvzQ7vBGrfDx84WgF0QeDr4kwoG2VUfo8vYK8k6kzMYguTth7HepSDSs5fyJ/eseal6oPsLAqQ5uLap0YPkwyyChNBGJ/RBb9qAi9BlmO+c7N6qvNe+6nSrGvpF4QEOWxI8AFZNMQ2Bu8GczE5P92uQ1xVXXKuFoVv7ABBbxDzT/ASX2tfClgoJw4Xi6zKMxRLS9fOziE3fssWw45FRGIQrNH/HoRXhtjA1OPHLaMskdkxHbkGr2mY0K9DikL/WYuuRa5U8jtEnXTuOnRWWrlHk+KHf/UtPw82tcfdZYcmqZILxgv4DN+qIP76zZh6TunFvFUZ+lPpDgchG1YsVE8WaoI13aaQcD4mnrvubblQ8dNHOC+zWOGwzeJRJuJGVxLLjyeSem3JYDK2rQ4opCY1e3lA3euxKKq8lxc9q5HrxO9cXjVEHY1bsULHcRhgpaF0EPw1Jc8KqT22vU4T/5SGWQ4JOlyhpA/8AkfuFtQpGX13tlx9nHH/N1w8bRVzcF9Z+Ch1kqlfL5YbWgxw9OHPMX/LY9i+jELLsIyYZnfwC0hjQM7uj1se7CwxjGWMjpDA40sR5kxV0q2uN+AW/qP4cY06vMZ8CU9b9ByfIbTExt1ZpT+uBwvamo0lJRKSdHVl1KqUN5fdBTv7rMDXzbhdOttagnAmlAKryTLQL/M8vzJ4riU/IpbsUam39dQwFYetkzRlq0dNp/yjCjLHVhk1WG/NJbldgM7Pxp5oDr2zXaNgnD79m5T2U5RP9WUUzBZMP5QEPZhhhDOWZO0GOQHlIukBN/8VE//BRVf9//S1+qYVUAfe7/WtxoHhfU4OfxWo4Vbo4eBAQX4T2NCaJkihaqwlWVijkxjIwR5lEileJVgm2N3nFImCsf/iCSacedDCkp5kVOno8YqK/0T9HLtshN419OcMyIFbxHHb50Xd8vwdHFsxfXw/u46bfas1SEh/zpQfdvy1SbIRf1u632XUMPnLTmRStBPPs4BSrJgRRIjgAh+ELD5bJnWVH8m/ZXCvtKuPEDGwMBr4VC06macpKnbSrljaDAeYa92b0E2D8tTF2HCz0edS7W2HmwtcuXIw874LDD8fBfBpvWOV+sb3izrx7PcNliMUZ+ueigrysXpKciLe5Ik3Z+eNVjYeeTunfOqbupRQLIzf0Qr7kgvexWNz8G64jwquMoL6Ja9i4NzCwVV/Rc4/UHqsaEj3kSehUYUdFFEphGAzzDVV682oDQ3KtSnfsGdqyR3aH15R3kKJLykdLbVWouvzqm9ChT2SLfDzu21uaLoMKdAs/Ux1f6ZCcF6q9xNX8G3YTYviy1K/B/FAAnlUmVpu0AKC+dsUsDcsmUcEqK4dL68Gp0IRO2saRcuJZnLmFA6nT2AStOjzbMorXNjD6ZJxpoNKiFOCpJCUUjKnim/8dkFtLDaGLBek58AS5iIzrqSYsJVVx8GO+wOPnECgOTfGSfkoJ+EnB9HF4tS02Wng7cSsVQBtvlEySm0xpDvAiXJfHd0G/RDoaB/Z5Hk7LhsWHE9j5Mekb64JMSu8tiw5XcJl+4klpTmgK+kxdrd/B+b5w+hyKLXQ8DKXymT7AVrNPf4HCiagI07SZ9z/tGLF4OGcyDcYU9Kr5rXL3WLCtMqESDrA7f3VrDqA6P+PKcm2Q51R/H39Nri+Zio63QL8UvMEqilJOIkUnRpG7Caej4NvXCpGRsfkV7gjHCIVjNhxKaXx7VR4KKON6dWvoi4nR4UWlE6i3Tn1EbzDnDIR/5XVrNJbKwNX8H0jYYBrJTPASTkro9WnTI43J/7Ik+GvaAk67UuYq0MLvrdE6hVGUZa6TVSCUuwRouV/rbRe3ap8NrfuyKQGRLJ9ecpTZXzfpSEszwRXNeun7qneeNIOmugEwNEZO04OGYCLSL26MAm68VykDZjfLnCcXvIRwWwRnTmhggbc0myQMumTppAEKNdDCY2WKHONZxwcBVwSu8Ka9S51/yJzMisLvaEQOlQZAy46RyPitTbBR16iYC+8iQgeNbVkGy7lSWO0+alw+XmebT7nRJ3ZvDv1/I5DhPbwfVfyeLp02KJvvK5AwH1JdjX0jbXP8zBjY1hnAXmiABmBUp/UrQJr5kYbW1YwCcjqlILJz95q1rkHWBaJmovqvwTHse/24hBT7Qwutgt59FBW+qGir1HMBvSAhf5qov6xwasrkYDJdKyen/2IytUd7qeSEcj7SxupotZFrLLWQ34gAK8BcNrQWKaNEokTpzLrJeRWvZN2TXTFKBh5CUGNYrr36WaZr2zlInqT/4qXeVbMQp2vvImg8WLdGLa69b7EAXY/kzEiZRqsXFhOlUdvXkUMJXzy+eP0GKVhbDYzGOxJOqo0mNzslfKIxWwFY6nuqpR0hHtNOGYnlEhfygwRL3AfNaCUpfQMlk6cTRwVTG+Ulv+CLxcYgTsS89SctsyQ6tBeTSiQQgAoFeQjijRHRLGVFaHCRsHKFSTYFy5Ch1XV7EnWKZqOKqx5PrTpuM2cDiRZ/0eAUSTPjF9Alu9rHTutOGIYJRsXr3XGugBa6rQQBtMuWt4J6aSr7E5YEFX+fK22tGtp/e/8g2jQYil3ipsCwJiRk85Qt+Ojy5fl5i0h1Wq0eKr/7YfJdeOwK+AkerZ4SbB0o9SO7OfF25xpymZtqlu90tJKs2F/jhQaQF/9RLJ43vZVf1xBG5a3aTw+VOaFl86O0p9KxbHm5VfaVe+Ry8hwQ13j7tTxKrbSFAxjSpY+pY+yPzcfmk5RQb1hjq9jNH2eTdo+m0BR2Hb6iG0eslUrdTNbBELRzEkpTU822T1XpXg1CbT+shgF5rSV3z+20aa9reJBrRdf2Dro1e6xTuGBGOmgrAr1NsYv9A0YkYM0ABCJxgG1siEJfkMpdFEBMZ3s8/wpN2AxRs0KgghnlDSEdtk8tITnME0ZKS/+/oJe7t4M4oCFWLNfA/Ri9/xHUhfmdWTFaJ3kc/+wH7AaAxrLMPXcpEYTnSkrSyn7uJovhueOQFQByVO3KNOqd2uueg9GkQ5NQkYaZSmPYuPkJqzSsXa+EabIOsJEp4Hnx2PO/QdRaEdxuL1P7n6X0vY7SniVlUlYcKCOJhfpohNK9pkWNUiE4/6PFQmpq78s2BlsFKBtsQXW4BOA2O+0gSaUAPZSIcZsOZ0Mzoh1RTOp0D6AHXlOmwrKThbOSQThZD2PHdr8Bpr+iA39ehXaZG9tKs3RwXq6xOyYplUIwpH2stg51CVwyt/MkG/frFEabEnRhl/4FXayMXwfbS30mYu5mfgFDUPXel8HZnPQOrpHF9RsdG2pGNtJw/LcHUf44W3s1tnSbfLdOh4maYwTqW+jnhqvtn+RWVSjtTOKIAAAxWoxY4gIuZBQyvBL6qkRRNvUU6c4wQQMM0FuO0kIbehW6n2kbzIADrYX4oe6MvCj1fddBmWfNtPgSeGeaqm9mBOMiZt1f05ft+qFEVUunepBYr230k2CPD4fiS9n4nvkcXtRDKE0wewu2h+PB4OO7SVR3lGWlygncLP9v3D94STloUVau2G5w9V+GDi2+begAoM4QOQPTe0bTFRxIjaGnQSaE+bb7tG7WvTgYuYTVKze/vgSKGHaxpiAJkgEz3CcYWzsaaHKefHrxQRVmrhji8D8NX/g1iWpUoR4vGzU4Bf0pGHLuwon4j3iPyWqTIxBIY+Fp7D3c1v1KavR3yJIK443JNw/n1JrEYVydNl+IFnHx7ZJnKh/GMn8NYViZ92KcyPHN0dAKgrNzt/RUTelywa/6PdnJ+RWR6W253TqPrqQnIzOyvF905oH3orKsIuQ6s02RHaNRMUUplOl3KpeKI0jM7Jt6yYoLUtnktUqf3EHjyKaxj7boO+i++cS9YSMxk2LwQBY3I0HtIDd7EQSsinAPjjgAcDHJ4pZwdMRZXtZp9fxhx/JbvkCB95SlHCcI4kBTNMONp+xk36mz944S+RHjQG8hpoSJnp9BcUPjfOADGJMQwDYTM1BD3hp1fPNaOIdaIAmrOTZu6roDp74zmSDb1fbqui+rNpYQNDJdtRuH2e+dlbckJXGtFQ2398Y0uoCvkVb6QB2h6bsi8yPl9ITMVPTyQPJDY5ya2yxGP9dJVji69rEHHglYa9CBDKJn/Q04FD4ouXW6YQ3bhj03LOVwrQqn6RTxALsuKDeuCMPjdvjlOfFYZ5x0HJypbJ+BwyMFwzEcitTQ0eur7kLXrryuBWV3eC7d20L9hHqB5fWNoD8acetkHUs5U4J26Rvy777fNwuzYX3H1+sq5+s9DL4/5O9q159/Ec3wJgMQxuwF2Oh9i5HpqlpyEzvb9qz3gmkaymW6f/Zn3vNGNVdnCavKyI7CWjJZxY2yD5ybr9Uc74i9C/xuEcIyhYQ1gFEbt786c3SAmyBTpssTQk1HLvoQpdCTb1ETK9hZhccwiyEVnLNur0O4xZD7dpa34kqA54EOstBjYuvd7SwIh9BCz2E9S1RB+jCEQb+7UdA1Yrhxc/jDQK0A6NmWkclCdYXUCyxY1I1q5UaDUAtkZg+oHtNYPimQnEjF5hDTTfrTqlSczliMYPopCnvm73ywNW5rb3xY9LFxTFW7Pn94/YeJBqNTBhZ9DSXIYt8u60G4vLV85geDkOeW+GqWacwx2WgdRfylZJxOsx3XHLZbFeHHlYOm+zv6NwGZwV9E3INSyVc0TrErJ36/UkZsOfWF0H965RYUvyuvGPiMyOaGe5lb45/xOIUY5EiXS7kGvKxsiHt+DrTvmuZeOTd21cuP6y2UAFi6qCgw8lSbsl0NZFqm7ZwfyxyYkxgV0wZv88nuA485NjHE7K1iXjHqcMk7J+XK1F8sX1JLyogNC37H2l6NOEVdPif4TGKn5Gr/Vlz5+iPTra5AzepvjXpx7k8/btlsH0IrPRL2QO61R9exhVdtzVjxVo/uCRySBkLF3hIU6Udet4vXu4tU8Wy6WNqlOVDbaBshDMqxEQYdGKzVWTl/i6cP1I8wrgzHAmu4k4sqZlzAHLqC3IIIUAXkpVJbdhtr25y7hi5ZPgtyDwELDpOSSkROAxmuuORR048+XwRm+vUOns2IXkZJTTrt7/PT7aqJdWXz6vm7sqKPm3lXbYCttZUVi6lOBB4vncFAIo9MtaTDHScLAuThcYgEVb1rGnVzj71K81Trf4OyhBaX0udSTU+QCUIkDsINkQ5LwR4ejvKYOEqU4ISASB8x4B/kR87m8jM6zv6C44Y57IW4anyFGOdJCRdMU7nBaPrFK7kkk8MaADqRc5NoL0w6tYNT/5PVWA6U/QggyD50QAR/CzozdIBfYDI/FuUMUWHSLejdACEOaLhyKAWIwnKvM1mSy61EVnT/CAOwGmRi7GRq+GM6gR/ycteNvXC7v1jW2Cxf+BdRxOS2cBOcH4s4tUs5DT3I94zjqRkkmQKb0gFZHbghtJ5FYXZIySI21RKeVjvzW3UqtCSWaILdWiAiEvxTSgZE8UO/T5Vl4Y/Xy5HKwa7Suyyxbax4usowoRzBUvX0F1AyFqJOK2ptgdXZbJXE6ph5eGjWjVOMq6zTtOv4CKt8GPXGu7sa7HwiwM7gA++hkA6T+PfXExakbCWQRjWnHqHXHSTbX1Vu7/9wosjNUQPrXUS8luMEpcon7Bgj73RdhEt8RJhj4xIhTAoI1QgKaR4vo9UNZlfbjA65SEcYQ0ZKLrVpdnE3HrxvfhxwtwaoOFWT9fAZlkz2YejVDDVbPqpX45PKPvE+Fu4VLtlNIA/NvpNrhVRH+Ia9ExYxGiQ9un7njxiPFs=,iv:pHRYaxmcQZ8ga2oeH6MZ7lUC8/NxZPAp+o7+JE392Qk=,tag:XYXyj2GmXSGd54UwHW+Scg==,type:str]
- NEXT_PUBLIC_API_HOST:
- _default: ENC[AES256_GCM,data:Ymv0EXpev9qEu9TD1un/3muOM0BDPeTjBZIvG7Off7u7iHUOl7NHZZUavNxUIw==,iv:j3W5n0LUnmwSLOCcuMeEITcDf4YImfR/2iJqyQOO5KQ=,tag:BYaIMnVRpO4CM65mYiDP6A==,type:str]
- NEXT_PUBLIC_APP_HOST:
- _default: ENC[AES256_GCM,data:OVcDHK/0UXBGlQyitKU95Rkct3EKirhOGKjZLPKqQVzsgf3YBfsof5st74dGWw==,iv:gqh0X94J56InUJJSv03/4fTJEJIBSVLlTE45FvnHv2c=,tag:Nwv6gbhANg8MoR8TIzvlVg==,type:str]
- NEXT_PUBLIC_STATS_API_HOST:
- _default: ENC[AES256_GCM,data:Zp5lp7hwy0ivPPUGyPjb0FOsrbJIaySDXD936GnZm20ZXirWLcoCY5xr6rPlQE0NrSNDnMt9cwQ=,iv:r9HF3gtwQPc+OyGsQxy2dmnC6q7+vYiQJrZrlWo8jkA=,tag:/rS6US+f2Sv8q+jW2VZPeg==,type:str]
- NEXT_PUBLIC_VISUALIZE_API_HOST:
- _default: ENC[AES256_GCM,data:t2vaoquUrkdXpszKFqktjDkpPzw6ot/jVmCdYZKto7HVJjVjxlogdRhTjDdB2iw5WQXLOuKuDN9Odd4AlQ==,iv:amdYXGuCkyqn3gPBvKMYBFiOOP43gkKGMeJH2MgZHbE=,tag:4ecKv5hbmC2sRrlGtSsrXQ==,type:str]
- NEXT_PUBLIC_LOGOUT_URL:
- _default: ENC[AES256_GCM,data:GzimJRj+bBPa1P/vob1NbARs2GPRnxr2kc8Ypx6lNHcBFXU4NXt8tVlwUofcwZn2,iv:R2qwSSDCpfAka2VUnXBVU+iYj74A8TzsTvWMAVrLWo4=,tag:Ly7HG+cUqm1Gd+OdUr9EQw==,type:str]
- NEXT_PUBLIC_LOGOUT_RETURN_URL:
- _default: ENC[AES256_GCM,data:+RrUv6rE/9aKVQ/W6sBTOjN56L7Et0eroTctStkr9RKfbYtSsnEGQ+aCtFoGvkxyDAI5AQz02dMQDO5UOhMVzp8=,iv:hKir85sJ3+Ghbxu8gj4l0VGacMaGWGEq1WYfW6Nuj64=,tag:AWaDR7PNZE1y/ZEg/XQWTw==,type:str]
-stats:
- ingress:
- host:
- _default: ENC[AES256_GCM,data:sZpjhZvk8u7hwagYSl4+C4bFEodiK+iiic0Zu3EGNEjH+aH807LC3DC9TpsTeUNZ,iv:H1k1bmKiFEbTurG2FJsmbuPIOgY3YP4DjuoJXFb7DkY=,tag:Fw85Up5Cm2jki+t/E5T0Qg==,type:str]
- annotations:
- - ENC[AES256_GCM,data:8uSrvKNXgKOlxc4qY8Slgwr8e3D8Dub/brwe9QYKGFV+K6qzhU+jGAR2/v+kRer74t+ZR+FuVucGrgL7S5jjDQHdX8c7b/qFP1I2euOWOuuxiAEl8p0bLfLlUWB6pNSFCTT0pddfRKRJq9nVUhVxs3m+qFQUqCqbfyK1/4d4sxMX8GMMCC0wopwWeAF+v6Dx8Je34fU1y3MSjI9Ir7ZvMP/A55MGJ49NfCrfmKCnXn8khmBhFww2SNH7TPrx0EbMY9Vs/pgMEtCSTNZbM6wpKNQonawrIrnM/TwLXI25KyY=,iv:z7SbnZ2OT8TWpv5bsvk68KLs3R7lPAXxyZx3F0I9Bes=,tag:gVJu/qpy0hGvBeDi40xbzw==,type:str]
- - ENC[AES256_GCM,data:1m6bfhJ30mQCKSyuW2ZB4ljsAB6nJSUSwQAP15ctU8iw8COZwUthH1aoRaY4D+IGtAQXtoUES1ezqQ==,iv:d8QE5WjV5PKebu6h10z7KXqoZMGZgO2+tb1s6DUe6ok=,tag:JtpwFc19RxVS/Hd4WDBFSg==,type:str]
- - ENC[AES256_GCM,data:pb+c5IE+pDi5X+xz/D95KMely3BRx0AQF5Un1T5PZ8Fuqw588TDmgzACrVPHmawiLhtflj/zY0w4Q5vSNySaWK2u+dpmJsWDT/aXaLzQIlDtqh9sjQo=,iv:0fUIuucihrilgcxPEYEfKTujBGgJ1Txkdnd3ejoMKrQ=,tag:W6A+748GYKfDCPMzk0pYJQ==,type:str]
- - ENC[AES256_GCM,data:DIOfgK+gt86iBiLRaHBD7KDnWqW2h35WI/0wbXSY9frW9Hjlx69+uPHqj3I7VG8=,iv:n7opDpqF5cj2xJn5G1HB3AbfEN+BkZE/X2th3kxabQY=,tag:JONAoISL6wa5mUAt6Wr26A==,type:str]
- environment:
- STATS__DB_URL:
- _default: ENC[AES256_GCM,data:kumdg7QQc2A/hPW6x3nySrpzlwG7HyOJHLFg448+7R944H1Nb8QXEckqOFmvQB0NTYSg6ydOgsDNEJZz,iv:SFOifTQ0W0OUX4HxufKtBpDLPrPG0o7vNbZ/+7lX19U=,tag:zxPYEMeSfWh9A82HB4O2pw==,type:str]
- STATS__BLOCKSCOUT_DB_URL:
- _default: ENC[AES256_GCM,data:+rN8WyD6QRK/I2PlM8pLErsaQXcaN3is8wlOAe04Dkp0TY+OyhTS8eGdDnzKdEml12YZshz/XImkNJSHHdAulCs=,iv:S2dtEQDReW1n1LPOBL9eV4tFDtyTY0axrjlwZMxUsnw=,tag:/pa5bwa5FH293C4xgMfS4Q==,type:str]
-postgres:
- environment:
- POSTGRES_USER:
- _default: ENC[AES256_GCM,data:hXEpOIBfSLE=,iv:nyiIipUKCS8OBXMqHbZARztemsoKRvyk0z7L6Okd8zo=,tag:MsyfRUipTTFiW3TJbNDcYw==,type:str]
- POSTGRES_HOST_AUTH_METHOD:
- _default: ENC[AES256_GCM,data:0Gge77A=,iv:+/lWnof/k7LvjUAcg1xPMEgcIMklSrF/OYuqqojivac=,tag:l6wG5IvYYaTCgJfounIR2Q==,type:str]
- POSTGRES_PASSWORD:
- _default: ENC[AES256_GCM,data:4AjRIf3NTW0=,iv:ECek8lT9eKca0gvTbXV9L1ucRuRoWoZNj5Vd3RtxBxQ=,tag:mezpd+nWbh+EbwpAebunYA==,type:str]
- POSTGRES_DB:
- _default: ENC[AES256_GCM,data:aBfXcDN1/Cd60w==,iv:tC3xv+uqck90yXn3Ye1FYqVXDdv/7wkluCO5D4dVEus=,tag:UFWNDKWWeDwPs2Cksf+cTg==,type:str]
- files:
- list:
- init.sql: ENC[AES256_GCM,data:kTAyepHKX1b1M8JVUMMPD2O58AO/Gb20G5XJYClunCAgHXWzJVybmuUqW0H+wo/7FIWGnn1LnbUYpQ6HtkSRvQehJ7vpOR3wG0SS,iv:RVzbNfjaMhS+An361AEoRxBkPycDT1rMhAoL7GTMH9A=,tag:95u4/FUoeJvz0PiP6Qplkg==,type:str]
-scVerifier:
- ingress:
- host:
- _default: ENC[AES256_GCM,data:RghlOyQTbha0Kuo1jaMt/wy+QS8HDCFXC02JysC8SS/GPaSe2WgSGwLtUBF+9gnHDJ///8/HGhoBgA==,iv:n2X0uZyO1TDFx1J3glmdCRsk+OfsMezAOXWWXYCMu6c=,tag:Bab9tMDfWxRvjzxEpNIMIw==,type:str]
- environment:
- SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__ACCESS_KEY:
- _default: ENC[AES256_GCM,data:YFP7tsoFlDHdtv1u22e8dea7aV5o+M8p,iv:eH5OArjzABIx60DMDgq0vdnAYS5x7mln4iddFaoLbkg=,tag:HEjR41aRgK5FhGvSa0WN1w==,type:str]
- SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__SECRET_KEY:
- _default: ENC[AES256_GCM,data:xitivq3rWyvSGrGXfg3XGylD2yOcGTwwEdXbkedq/Z3cF4lry34kJw==,iv:lb1J1qqGod900fIIsP3bH5uM/Rr3XKvQvO3+d0x5XtA=,tag:aLzbXbMCIYUzIEeK5QGqmw==,type:str]
- SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__BUCKET:
- _default: ENC[AES256_GCM,data:ezHIeKHhAhOJAXrEYQ==,iv:aoXA0e3kFaYr2NPZQbpaiVmBl2MCzoGVJBMmHJU3xuo=,tag:aZ/WqfLFXQ2B/o86A7BZQg==,type:str]
-sigProvider:
- ingress:
- host:
- _default: ENC[AES256_GCM,data:MCWXySvrj4+rL9UdYY5DYsfFCWQ7LC0dHlkeH2BNoLo1ep8CihvlD96W/jiEsNfVJQgwxKGziw==,iv:xADLeEIMxpAhspztXQuuN734gSTfR9haunfozL/IpHU=,tag:c56OXOIomKULIoqHiM4fcQ==,type:str]
-visualizer:
- ingress:
- host:
- _default: ENC[AES256_GCM,data:ogYiUKW6vVFfl+r4U2gQXi++HZagi85WJp/mXKKDhTLaaSTBXOIQ9faWVdXzGZDWRPMhleo=,iv:YFh+Klw730IzWipjq5XwW+o+tAt7s94V+4bdo61WHO8=,tag:D8AsZxoRi3lg0RlGioMklw==,type:str]
-sops:
- kms: []
- gcp_kms: []
- azure_kv: []
- hc_vault: []
- age: []
- lastmodified: "2023-04-26T10:43:55Z"
- mac: ENC[AES256_GCM,data:YXMsmP9rZBcN6HUbR34v2OY8XpllCKLhoR4uBCru/fJeA76DNdIhwPDSDLA5XIfpMNIswq4bE7Oc6776gzkBLyiAALOPIOWI4MAz4qRl8jX7jSRRkxvEcEEX09dq3lkej52BeKvFAjTSdnfSed0IwWt62yqaic2NHmXvGbkSnUI=,iv:X+KDKi1y/F5qBS5vPz+SxxyEmDKTWDc/k4ivhRxPNKA=,tag:asIyutQ2veDiIwRvC9S6Kg==,type:str]
- pgp:
- - created_at: "2022-10-10T07:37:16Z"
- enc: |
- -----BEGIN PGP MESSAGE-----
-
- hQEMA1MXzg1c4SMLAQf+Kyj8/Ws9rdLdRWh0G8kR+Ni2gBPnHkcRgA5ol845Jpyj
- p61tlQX6lPyJVKE9vpbSGk9JDX6Mpiwp+qfXlcmR91oUubPUl61e+vjZST+lk/G+
- nkaKCAo0TnUSm0vJGDEkbV26gO5JT71Ziq/k2Pw7IZ3UYmuq19SMdzdG/2xsWZ+g
- zKPdw/XVw0SYMSha6TnMDIoCuZDlstLmpBx2AktujiSDBJE5cz1i0Vuqnz5xOi6Y
- CafMxrQTiPD4ljV+Hzckvf4tLsiU441x/g3x/VgDjtDaofPMa4dVVRpSSLuiIFN+
- TePyzQE/PnDZUbwy8EKUr+GDslm2ch+WUhpZaGOnrtJeAZ2PEXLJeNQbqs5nnnBO
- Wa+BNuV0H/OaVHL34GNFvgyeiWqlsAc/V/QFBRBS4K5G9XOqKH1hTidFTc+Nd7dh
- i4qobisBGW9mm4ecmKyzrSB6sVijfPbv7VQ4UFO8xA==
- =1Uwc
- -----END PGP MESSAGE-----
- fp: 99E83B7490B1A9F51781E6055317CE0D5CE1230B
- unencrypted_suffix: _unencrypted
- version: 3.7.3
diff --git a/deploy/testing/eth-goerli/values.yaml b/deploy/testing/eth-goerli/values.yaml
deleted file mode 100644
index 1788be022d2d..000000000000
--- a/deploy/testing/eth-goerli/values.yaml
+++ /dev/null
@@ -1,438 +0,0 @@
-global:
- env: testnet
-
-blockscout:
- enabled: true
- image:
- _default: &image blockscout/blockscout:latest
-
- ingress:
- enabled: true
- tls:
- enabled: true
- createSecret: true
- # init container
- init:
- enabled: true
- image:
- _default: *image
-
- resources:
- limits:
- memory:
- _default: "8Gi"
- cpu:
- _default: "3"
- requests:
- memory:
- _default: "6Gi"
- cpu:
- _default: "2"
-
- # node label
- nodeSelector:
- enabled: true
- labels:
- _default:
- app: blockscout
-
- environment:
- ETHEREUM_JSONRPC_VARIANT:
- _default: geth
- HEART_BEAT_TIMEOUT:
- _default: 30
- PORT:
- _default: 4000
- SUBNETWORK:
- _default: Goerli
- HEALTHY_BLOCKS_PERIOD:
- _default: 60
- NETWORK:
- _default: (Ethereum)
- NETWORK_ICON:
- _default: _network_icon.html
- COIN:
- _default: ETH
- ECTO_USE_SSL:
- _default: 'false'
- COIN_NAME:
- _default: ETH
- LOGO:
- _default: /images/goerli_logo.svg
- TXS_STATS_DAYS_TO_COMPILE_AT_INIT:
- _default: 10
- COIN_BALANCE_HISTORY_DAYS:
- _default: 90
- POOL_SIZE:
- _default: 300
- POOL_SIZE_API:
- _default: 10
- ACCOUNT_POOL_SIZE:
- _default: 10
- DISPLAY_TOKEN_ICONS:
- _default: 'true'
- FETCH_REWARDS_WAY:
- _default: manual
- SHOW_TESTNET_LABEL:
- _default: 'true'
- CHAIN_ID:
- _default: 5
- MICROSERVICE_SC_VERIFIER_ENABLED:
- _default: 'true'
- MICROSERVICE_VISUALIZE_SOL2UML_ENABLED:
- _default: 'true'
- MICROSERVICE_SIG_PROVIDER_ENABLED:
- _default: 'true'
- INDEXER_MEMORY_LIMIT:
- _default: 7
- ACCOUNT_ENABLED:
- _default: 'true'
- API_V2_ENABLED:
- _default: 'true'
- APPS_MENU:
- _default: 'true'
- APPS:
- _default: '[{"title": "Marketplace", "url": "/apps", "embedded?": true}]'
- ETHEREUM_JSONRPC_DEBUG_TRACE_TRANSACTION_TIMEOUT:
- _default: '20s'
- ETHEREUM_JSONRPC_HTTP_TIMEOUT:
- _default: 60
- INDEXER_INTERNAL_TRANSACTIONS_BATCH_SIZE:
- _default: 15
- INDEXER_DISABLE_EMPTY_BLOCKS_SANITIZER:
- _default: 'true'
- INDEXER_RECEIPTS_BATCH_SIZE:
- _default: 50
- INDEXER_COIN_BALANCES_BATCH_SIZE:
- _default: 50
- INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER:
- _default: 'true'
- INDEXER_TX_ACTIONS_ENABLE:
- _default: 'true'
- DISABLE_EXCHANGE_RATES:
- _default: 'true'
- SOURCIFY_INTEGRATION_ENABLED:
- _default: 'true'
-
-frontend:
- app: blockscout
- enabled: true
- image:
- _default: ghcr.io/blockscout/frontend:main
- replicas:
- app: 2
- ingress:
- enabled: true
- # enable https
- tls:
- enabled: true
- createSecret: false
- path:
- exact:
- - "/"
- prefix:
- - "/apps"
- - "/_next"
- - "/node-api"
- - "/static"
- - "/auth/profile"
- - "/account"
- - "/txs"
- - "/tx"
- - "/blocks"
- - "/block"
- - "/stats"
- - "/address"
- - "/search-results"
- - "/token"
- - "/tokens"
- - "/accounts"
- - "/visualize"
- - "/api-docs"
- - "/csv-export"
-
- resources:
- limits:
- memory:
- _default: "2Gi"
- cpu:
- _default: "2"
- requests:
- memory:
- _default: "512Mi"
- cpu:
- _default: "250m"
- # node label
- nodeSelector:
- enabled: true
- labels:
- _default:
- app: blockscout-prod
- environment:
- # ui config
- NEXT_PUBLIC_NETWORK_EXPLORERS:
- _default: "[{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address'}}]"
- # network config
- NEXT_PUBLIC_NETWORK_NAME:
- _default: Ethereum
- NEXT_PUBLIC_NETWORK_SHORT_NAME:
- _default: Goerli
- NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME:
- _default: ethereum
- NEXT_PUBLIC_NETWORK_ID:
- _default: 5
- NEXT_PUBLIC_NETWORK_CURRENCY_NAME:
- _default: Ether
- NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL:
- _default: ETH
- NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS:
- _default: 18
- NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS:
- _default: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
- NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED:
- _default: true
- NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE:
- _default: validation
- NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM:
- _default: https://airtable.com/shrqUAcjgGJ4jU88C
- # api config
- NEXT_PUBLIC_API_BASE_PATH:
- _default: /
- NEXT_PUBLIC_BLOCKSCOUT_VERSION:
- _default: v5.1.5-beta
- NEXT_PUBLIC_FOOTER_GITHUB_LINK:
- _default: https://github.com/blockscout/blockscout
- NEXT_PUBLIC_FOOTER_TWITTER_LINK:
- _default: https://www.twitter.com/blockscoutcom
- NEXT_PUBLIC_APP_ENV:
- _default: staging
- NEXT_PUBLIC_APP_INSTANCE:
- _default: unknown
- NEXT_PUBLIC_HOMEPAGE_CHARTS:
- _default: "['daily_txs']"
- NEXT_PUBLIC_NETWORK_RPC_URL:
- _default: https://rpc.ankr.com/eth_goerli
- NEXT_PUBLIC_IS_TESTNET:
- _default: 'true'
- NEXT_PUBLIC_API_SPEC_URL:
- _default: https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
-# enable stats deploy
-stats:
- enabled: true
- image:
- _default: ghcr.io/blockscout/stats:main
-
- # enable ingress
- ingress:
- enabled: true
- # enable https
- tls:
- enabled: true
- createSecret: true
-
- resources:
- limits:
- memory:
- _default: 128Mi
- cpu:
- _default: 100m
- requests:
- memory:
- _default: 128Mi
- cpu:
- _default: 100m
-
- # node label
- nodeSelector:
- enabled: true
- labels:
- _default:
- app: blockscout
-
- # enable Horizontal Pod Autoscaler
- hpa:
- enabled: true
-
- environment:
- RUST_LOG:
- _default: info
- STATS__RUN_MIGRATIONS:
- _default: 'true'
- STATS__TRACING__FORMAT:
- _default: json
- STATS__METRICS__ENABLED:
- _default: 'true'
-
-postgres:
- enabled: true
- image: postgres:14.7
- port: 5432
-
- command: '["docker-entrypoint.sh", "-c"]'
- args: '["max_connections=500"]'
-
- customShm:
- enabled: false
-
- files:
- enabled: true
- mountPath: /docker-entrypoint-initdb.d
-
- persistence: true
- storage: 500Gi
-
- resources:
- limits:
- memory:
- _default: "2Gi"
- cpu:
- _default: "2"
- requests:
- memory:
- _default: "2Gi"
- cpu:
- _default: "2"
-
-# enable Smart-contract-verifier deploy
-scVerifier:
- enabled: true
- image:
- _default: ghcr.io/blockscout/smart-contract-verifier:main
- # enable ingress
- ingress:
- enabled: true
- # enable https
- tls:
- enabled: true
- resources:
- limits:
- memory:
- _default: 512Mi
- cpu:
- _default: 250m
- requests:
- memory:
- _default: 512Mi
- cpu:
- _default: 250m
- # probes
- livenessProbe:
- enabled: true
- # path: /health
- readinessProbe:
- enabled: true
- # path: /health
- # enable Horizontal Pod Autoscaler
- hpa:
- enabled: true
- environment:
- SMART_CONTRACT_VERIFIER__SERVER__HTTP__ADDR:
- _default: 0.0.0.0:8050
- SMART_CONTRACT_VERIFIER__SERVER__GRPC__ADDR:
- _default: 0.0.0.0:8051
- # SMART_CONTRACT_VERIFIER__SOLIDITY__ENABLED:
- # _default: 'true'
- SMART_CONTRACT_VERIFIER__SOLIDITY__COMPILERS_DIR:
- _default: /tmp/solidity-compilers
- SMART_CONTRACT_VERIFIER__SOLIDITY__REFRESH_VERSIONS_SCHEDULE:
- _default: 0 0 * * * * *
- # It depends on the OS you are running the service on
- # SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__LIST__LIST_URL:
- # _default: https://solc-bin.ethereum.org/linux-amd64/list.json
- #SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__LIST__LIST_URL=https://solc-bin.ethereum.org/macosx-amd64/list.json
- #SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__LIST__LIST_URL=https://solc-bin.ethereum.org/windows-amd64/list.json
- SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__REGION:
- _default: ""
- SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__ENDPOINT:
- _default: https://storage.googleapis.com
- SMART_CONTRACT_VERIFIER__SOURCIFY__ENABLED:
- _default: 'true'
- SMART_CONTRACT_VERIFIER__SOURCIFY__API_URL:
- _default: https://sourcify.dev/server/
- SMART_CONTRACT_VERIFIER__SOURCIFY__VERIFICATION_ATTEMPTS:
- _default: 3
- SMART_CONTRACT_VERIFIER__SOURCIFY__REQUEST_TIMEOUT:
- _default: 10
- SMART_CONTRACT_VERIFIER__METRICS__ENABLED:
- _default: 'true'
- SMART_CONTRACT_VERIFIER__METRICS__ADDR:
- _default: 0.0.0.0:6060
- SMART_CONTRACT_VERIFIER__METRICS__ROUTE:
- _default: /metrics
- SMART_CONTRACT_VERIFIER__JAEGER__ENABLED:
- _default: 'false'
-
-# enable visualizer deploy
-visualizer:
- enabled: true
- image:
- _default: ghcr.io/blockscout/visualizer:main
-
- # enable ingress
- ingress:
- enabled: true
- host:
- _default: visualizer.test.aws-k8s.blockscout.com
- # enable https
- tls:
- enabled: true
- createSecret: false
-
- resources:
- limits:
- memory:
- _default: 64Mi
- cpu:
- _default: 50m
- requests:
- memory:
- _default: 64Mi
- cpu:
- _default: 50m
-
- # node label
- nodeSelector:
- enabled: true
- labels:
- _default:
- app: blockscout
-
- environment:
- VISUALIZER__SERVER__HTTP__ENABLED:
- _default: 'true'
- VISUALIZER__SERVER__HTTP__ADDR:
- _default: 0.0.0.0:8050
- VISUALIZER__SERVER__GRPC__ENABLED:
- _default: 'false'
-
-# enable sig-provider deploy
-sigProvider:
- enabled: true
- image:
- _default: ghcr.io/blockscout/sig-provider:main
-
- # enable ingress
- ingress:
- enabled: true
- # enable https
- tls:
- enabled: true
- createSecret: false
-
- # enable Horizontal Pod Autoscaler
- hpa:
- enabled: false
-
- # probes
- livenessProbe:
- enabled: false
- readinessProbe:
- enabled: false
-
- environment:
- SIG_PROVIDER__METRICS__ENABLED:
- _default: 'true'
- SIG_PROVIDER__SERVER__HTTP__ADDR:
- _default: 0.0.0.0:8043
- SIG_PROVIDER__SERVER__GRPC__ENABLED:
- _default: 'false'
diff --git a/docker-compose/README.md b/docker-compose/README.md
index 74a49e565783..cc472ebd4c40 100644
--- a/docker-compose/README.md
+++ b/docker-compose/README.md
@@ -11,42 +11,80 @@ Runs Blockscout locally in Docker containers with [docker-compose](https://githu
## Building Docker containers from source
```bash
+cd ./docker-compose
docker-compose up --build
```
-This command uses by-default `docker-compose.yml`, which builds the explorer into the Docker image and runs 6 Docker containers:
+**Note**: if you don't need to make backend customizations, you can run `docker-compose up` in order to launch from pre-build backend Docker image. This will be much faster.
-- Postgres 14.x database, which will be available at port 7432 on localhost.
-- Redis database of latest version, which will be available at port 6379 on localhost.
-- Blockscout explorer at http://localhost:4000.
+This command uses `docker-compose.yml` by-default, which builds the backend of the explorer into the Docker image and runs 9 Docker containers:
-and 3 Rust microservices:
+- Postgres 14.x database, which will be available at port 7432 on the host machine.
+- Redis database of the latest version.
+- Blockscout backend with api at /api path.
+- Nginx proxy to bind backend, frontend and microservices.
+- Blockscout explorer at http://localhost.
-- [Smart-contract-verifier](https://github.com/blockscout/blockscout-rs/tree/main/smart-contract-verifier) service, which will be available at port 8150 on the host machine.
-- [Sig-provider](https://github.com/blockscout/blockscout-rs/tree/main/sig-provider) service, which will be available at port 8151 on the host machine.
-- [Sol2UML visualizer](https://github.com/blockscout/blockscout-rs/tree/main/visualizer) service, which will be available at port 8152 on the host machine.
+and 4 containers for microservices (written in Rust):
-Note for Linux users: Linux users need to run the local node on http://0.0.0.0/ rather than http://127.0.0.1/
+- [Stats](https://github.com/blockscout/blockscout-rs/tree/main/stats) service with a separate Postgres 14 DB.
+- [Sol2UML visualizer](https://github.com/blockscout/blockscout-rs/tree/main/visualizer) service.
+- [Sig-provider](https://github.com/blockscout/blockscout-rs/tree/main/sig-provider) service.
-## Building Docker containers from source with native smart contract verification (deprecated)
-
-```bash
-docker-compose -f docker-compose-no-rust-verification.yml up --build
-```
+**Note for Linux users**: Linux users need to run the local node on http://0.0.0.0/ rather than http://127.0.0.1/
## Configs for different Ethereum clients
-The repo contains built-in configs for different clients without needing to build the image.
+The repo contains built-in configs for different JSON RPC clients without need to build the image.
+
+**Note**: in all below examples, you can use `docker compose` instead of `docker-compose`, if compose v2 plugin is installed in Docker.
+
+| __JSON RPC Client__ | __Docker compose launch command__ |
+| -------- | ------- |
+| Erigon | `docker-compose -f erigon.yml up -d` |
+| Geth (suitable for Reth as well) | `docker-compose -f geth.yml up -d` |
+| Geth Clique | `docker-compose -f geth-clique-consensus.yml up -d` |
+| Nethermind, OpenEthereum | `docker-compose -f nethermind up -d` |
+| Ganache | `docker-compose -f ganache.yml up -d` |
+| HardHat network | `docker-compose -f hardhat-network.yml up -d` |
-- Erigon: `docker-compose -f docker-compose-no-build-erigon.yml up -d`
-- Geth: `docker-compose -f docker-compose-no-build-geth.yml up -d`
-- Nethermind, OpenEthereum: `docker-compose -f docker-compose-no-build-nethermind up -d`
-- Ganache: `docker-compose -f docker-compose-no-build-ganache.yml up -d`
-- HardHat network: `docker-compose -f docker-compose-no-build-hardhat-network.yml up -d`
-- Running only explorer without DB: `docker-compose -f docker-compose-no-build-no-db-container.yml up -d`. In this case, one container is created - for the explorer itself. And it assumes that the DB credentials are provided through `DATABASE_URL` environment variable.
+- Running only explorer without DB: `docker-compose -f external-db.yml up -d`. In this case, no db container is created. And it assumes that the DB credentials are provided through `DATABASE_URL` environment variable on the backend container.
+- Running explorer with external backend: `docker-compose -f external-backend.yml up -d`
+- Running explorer with external frontend: `docker-compose -f external-frontend.yml up -d`
+- Running all microservices: `docker-compose -f microservices.yml up -d`
All of the configs assume the Ethereum JSON RPC is running at http://localhost:8545.
In order to stop launched containers, run `docker-compose -d -f config_file.yml down`, replacing `config_file.yml` with the file name of the config which was previously launched.
-You can adjust BlockScout environment variables from `./envs/common-blockscout.env`. Descriptions of the ENVs are available in [the docs](https://docs.blockscout.com/for-developers/information-and-settings/env-variables).
+You can adjust BlockScout environment variables:
+
+- for backend in `./envs/common-blockscout.env`
+- for frontend in `./envs/common-frontend.env`
+- for stats service in `./envs/common-stats.env`
+- for visualizer in `./envs/common-visualizer.env`
+
+Descriptions of the ENVs are available
+
+- for [backend](https://docs.blockscout.com/for-developers/information-and-settings/env-variables)
+- for [frontend](https://github.com/blockscout/frontend/blob/main/docs/ENVS.md).
+
+## Running Docker containers via Makefile
+
+Prerequisites are the same, as for docker-compose setup.
+
+Start all containers:
+
+```bash
+cd ./docker
+make start
+```
+
+Stop all containers:
+
+```bash
+cd ./docker
+make stop
+```
+
+***Note***: Makefile uses the same .env files since it is running docker-compose services inside.
diff --git a/docker-compose/docker-compose-no-build-erigon.yml b/docker-compose/docker-compose-no-build-erigon.yml
deleted file mode 100644
index d3c40ddcfdce..000000000000
--- a/docker-compose/docker-compose-no-build-erigon.yml
+++ /dev/null
@@ -1,56 +0,0 @@
-version: '3.8'
-
-services:
- redis_db:
- extends:
- file: ./services/docker-compose-redis.yml
- service: redis_db
-
- db:
- extends:
- file: ./services/docker-compose-db.yml
- service: db
-
- blockscout:
- depends_on:
- - db
- - smart-contract-verifier
- - redis_db
- image: blockscout/blockscout:${DOCKER_TAG:-latest}
- pull_policy: always
- restart: always
- stop_grace_period: 5m
- container_name: 'blockscout'
- links:
- - db:database
- command: bash -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start"
- extra_hosts:
- - 'host.docker.internal:host-gateway'
- env_file:
- - ./envs/common-blockscout.env
- environment:
- ETHEREUM_JSONRPC_VARIANT: 'erigon'
- ETHEREUM_JSONRPC_HTTP_URL: http://host.docker.internal:8545/
- ETHEREUM_JSONRPC_TRACE_URL: http://host.docker.internal:8545/
- DATABASE_URL: postgresql://postgres:@host.docker.internal:7432/blockscout?ssl=false
- ECTO_USE_SSL: 'false'
- SECRET_KEY_BASE: '56NtB48ear7+wMSf0IQuWDAAazhpb31qyc7GiyspBP2vh7t5zlCsF5QDv76chXeN'
- ports:
- - 4000:4000
- volumes:
- - ./logs/:/app/logs/
-
- smart-contract-verifier:
- extends:
- file: ./services/docker-compose-smart-contract-verifier.yml
- service: smart-contract-verifier
-
- visualizer:
- extends:
- file: ./services/docker-compose-visualizer.yml
- service: visualizer
-
- sig-provider:
- extends:
- file: ./services/docker-compose-sig-provider.yml
- service: sig-provider
diff --git a/docker-compose/docker-compose-no-build-frontend.yml b/docker-compose/docker-compose-no-build-frontend.yml
deleted file mode 100644
index b4ef7ac09a41..000000000000
--- a/docker-compose/docker-compose-no-build-frontend.yml
+++ /dev/null
@@ -1,89 +0,0 @@
-version: '3.8'
-
-services:
- redis_db:
- extends:
- file: ./services/docker-compose-redis.yml
- service: redis_db
-
- db:
- extends:
- file: ./services/docker-compose-db.yml
- service: db
-
- backend:
- depends_on:
- - db
- - smart-contract-verifier
- - redis_db
- image: blockscout/blockscout:${DOCKER_TAG:-master}
- pull_policy: always
- restart: always
- stop_grace_period: 5m
- container_name: 'blockscout'
- links:
- - db:database
- command: bash -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start"
- extra_hosts:
- - 'host.docker.internal:host-gateway'
- env_file:
- - ./envs/common-blockscout.env
- environment:
- ETHEREUM_JSONRPC_VARIANT: 'ganache'
- ETHEREUM_JSONRPC_HTTP_URL: http://host.docker.internal:8545/
- ETHEREUM_JSONRPC_WS_URL: ws://host.docker.internal:8545/
- INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER: 'true'
- INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER: 'true'
- DATABASE_URL: postgresql://postgres:@host.docker.internal:7432/blockscout?ssl=false
- ECTO_USE_SSL: 'false'
- SECRET_KEY_BASE: '56NtB48ear7+wMSf0IQuWDAAazhpb31qyc7GiyspBP2vh7t5zlCsF5QDv76chXeN'
- CHAIN_ID: '1337'
- API_V2_ENABLED: 'true'
- MIX_ENV: 'prod'
- ports:
- - 4000:4000
- volumes:
- - ./logs/:/app/logs/
-
- smart-contract-verifier:
- extends:
- file: ./services/docker-compose-smart-contract-verifier.yml
- service: smart-contract-verifier
-
- visualizer:
- extends:
- file: ./services/docker-compose-visualizer.yml
- service: visualizer
-
- sig-provider:
- extends:
- file: ./services/docker-compose-sig-provider.yml
- service: sig-provider
-
- frontend:
- depends_on:
- - backend
- extends:
- file: ./services/docker-compose-frontend.yml
- service: frontend
-
- stats-db:
- depends_on:
- - backend
- extends:
- file: ./services/docker-compose-stats.yml
- service: stats-db
-
- stats:
- depends_on:
- - backend
- extends:
- file: ./services/docker-compose-stats.yml
- service: stats
-
- proxy:
- depends_on:
- - frontend
- extends:
- file: ./services/docker-compose-nginx.yml
- service: proxy
diff --git a/docker-compose/docker-compose-no-build-ganache.yml b/docker-compose/docker-compose-no-build-ganache.yml
deleted file mode 100644
index 6789b5720ffb..000000000000
--- a/docker-compose/docker-compose-no-build-ganache.yml
+++ /dev/null
@@ -1,59 +0,0 @@
-version: '3.8'
-
-services:
- redis_db:
- extends:
- file: ./services/docker-compose-redis.yml
- service: redis_db
-
- db:
- extends:
- file: ./services/docker-compose-db.yml
- service: db
-
- blockscout:
- depends_on:
- - db
- - smart-contract-verifier
- - redis_db
- image: blockscout/blockscout:${DOCKER_TAG:-latest}
- pull_policy: always
- restart: always
- stop_grace_period: 5m
- container_name: 'blockscout'
- links:
- - db:database
- command: bash -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start"
- extra_hosts:
- - 'host.docker.internal:host-gateway'
- env_file:
- - ./envs/common-blockscout.env
- environment:
- ETHEREUM_JSONRPC_VARIANT: 'ganache'
- ETHEREUM_JSONRPC_HTTP_URL: http://host.docker.internal:8545/
- ETHEREUM_JSONRPC_WS_URL: ws://host.docker.internal:8545/
- INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER: 'true'
- INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER: 'true'
- DATABASE_URL: postgresql://postgres:@host.docker.internal:7432/blockscout?ssl=false
- ECTO_USE_SSL: 'false'
- SECRET_KEY_BASE: '56NtB48ear7+wMSf0IQuWDAAazhpb31qyc7GiyspBP2vh7t5zlCsF5QDv76chXeN'
- CHAIN_ID: '1337'
- ports:
- - 4000:4000
- volumes:
- - ./logs/:/app/logs/
-
- smart-contract-verifier:
- extends:
- file: ./services/docker-compose-smart-contract-verifier.yml
- service: smart-contract-verifier
-
- visualizer:
- extends:
- file: ./services/docker-compose-visualizer.yml
- service: visualizer
-
- sig-provider:
- extends:
- file: ./services/docker-compose-sig-provider.yml
- service: sig-provider
diff --git a/docker-compose/docker-compose-no-build-geth-clique-consensus.yml b/docker-compose/docker-compose-no-build-geth-clique-consensus.yml
deleted file mode 100644
index 4fe014bf8ed8..000000000000
--- a/docker-compose/docker-compose-no-build-geth-clique-consensus.yml
+++ /dev/null
@@ -1,56 +0,0 @@
-version: '3.8'
-
-services:
- redis_db:
- extends:
- file: ./services/docker-compose-redis.yml
- service: redis_db
-
- db:
- extends:
- file: ./services/docker-compose-db.yml
- service: db
-
- blockscout:
- depends_on:
- - db
- - smart-contract-verifier
- - redis_db
- image: blockscout/blockscout:${DOCKER_TAG:-latest}
- pull_policy: always
- restart: always
- stop_grace_period: 5m
- container_name: 'blockscout'
- links:
- - db:database
- command: bash -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start"
- extra_hosts:
- - 'host.docker.internal:host-gateway'
- env_file:
- - ./envs/common-blockscout.env
- environment:
- ETHEREUM_JSONRPC_VARIANT: 'geth'
- BLOCK_TRANSFORMER: 'clique'
- ETHEREUM_JSONRPC_HTTP_URL: http://host.docker.internal:8545/
- DATABASE_URL: postgresql://postgres:@host.docker.internal:7432/blockscout?ssl=false
- ECTO_USE_SSL: 'false'
- SECRET_KEY_BASE: '56NtB48ear7+wMSf0IQuWDAAazhpb31qyc7GiyspBP2vh7t5zlCsF5QDv76chXeN'
- ports:
- - 4000:4000
- volumes:
- - ./logs/:/app/logs/
-
- smart-contract-verifier:
- extends:
- file: ./services/docker-compose-smart-contract-verifier.yml
- service: smart-contract-verifier
-
- visualizer:
- extends:
- file: ./services/docker-compose-visualizer.yml
- service: visualizer
-
- sig-provider:
- extends:
- file: ./services/docker-compose-sig-provider.yml
- service: sig-provider
diff --git a/docker-compose/docker-compose-no-build-geth.yml b/docker-compose/docker-compose-no-build-geth.yml
deleted file mode 100644
index 55ff3227d48c..000000000000
--- a/docker-compose/docker-compose-no-build-geth.yml
+++ /dev/null
@@ -1,55 +0,0 @@
-version: '3.8'
-
-services:
- redis_db:
- extends:
- file: ./services/docker-compose-redis.yml
- service: redis_db
-
- db:
- extends:
- file: ./services/docker-compose-db.yml
- service: db
-
- blockscout:
- depends_on:
- - db
- - smart-contract-verifier
- - redis_db
- image: blockscout/blockscout:${DOCKER_TAG:-latest}
- pull_policy: always
- restart: always
- stop_grace_period: 5m
- container_name: 'blockscout'
- links:
- - db:database
- command: bash -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start"
- extra_hosts:
- - 'host.docker.internal:host-gateway'
- env_file:
- - ./envs/common-blockscout.env
- environment:
- ETHEREUM_JSONRPC_VARIANT: 'geth'
- ETHEREUM_JSONRPC_HTTP_URL: http://host.docker.internal:8545/
- DATABASE_URL: postgresql://postgres:@host.docker.internal:7432/blockscout?ssl=false
- ECTO_USE_SSL: 'false'
- SECRET_KEY_BASE: '56NtB48ear7+wMSf0IQuWDAAazhpb31qyc7GiyspBP2vh7t5zlCsF5QDv76chXeN'
- ports:
- - 4000:4000
- volumes:
- - ./logs/:/app/logs/
-
- smart-contract-verifier:
- extends:
- file: ./services/docker-compose-smart-contract-verifier.yml
- service: smart-contract-verifier
-
- visualizer:
- extends:
- file: ./services/docker-compose-visualizer.yml
- service: visualizer
-
- sig-provider:
- extends:
- file: ./services/docker-compose-sig-provider.yml
- service: sig-provider
diff --git a/docker-compose/docker-compose-no-build-hardhat-network.yml b/docker-compose/docker-compose-no-build-hardhat-network.yml
deleted file mode 100644
index 588d680bd441..000000000000
--- a/docker-compose/docker-compose-no-build-hardhat-network.yml
+++ /dev/null
@@ -1,57 +0,0 @@
-version: '3.8'
-
-services:
- redis_db:
- extends:
- file: ./services/docker-compose-redis.yml
- service: redis_db
-
- db:
- extends:
- file: ./services/docker-compose-db.yml
- service: db
-
- blockscout:
- depends_on:
- - db
- - smart-contract-verifier
- - redis_db
- image: blockscout/blockscout:${DOCKER_TAG:-latest}
- pull_policy: always
- restart: always
- stop_grace_period: 5m
- container_name: 'blockscout'
- links:
- - db:database
- command: bash -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start"
- extra_hosts:
- - 'host.docker.internal:host-gateway'
- env_file:
- - ./envs/common-blockscout.env
- environment:
- ETHEREUM_JSONRPC_VARIANT: 'geth'
- ETHEREUM_JSONRPC_HTTP_URL: http://host.docker.internal:8545/
- ETHEREUM_JSONRPC_WS_URL: ws://host.docker.internal:8545/
- INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER: 'true'
- DATABASE_URL: postgresql://postgres:@host.docker.internal:7432/blockscout?ssl=false
- ECTO_USE_SSL: 'false'
- SECRET_KEY_BASE: '56NtB48ear7+wMSf0IQuWDAAazhpb31qyc7GiyspBP2vh7t5zlCsF5QDv76chXeN'
- ports:
- - 4000:4000
- volumes:
- - ./logs/:/app/logs/
-
- smart-contract-verifier:
- extends:
- file: ./services/docker-compose-smart-contract-verifier.yml
- service: smart-contract-verifier
-
- visualizer:
- extends:
- file: ./services/docker-compose-visualizer.yml
- service: visualizer
-
- sig-provider:
- extends:
- file: ./services/docker-compose-sig-provider.yml
- service: sig-provider
diff --git a/docker-compose/docker-compose-no-build-nethermind.yml b/docker-compose/docker-compose-no-build-nethermind.yml
deleted file mode 100644
index 69725888e8eb..000000000000
--- a/docker-compose/docker-compose-no-build-nethermind.yml
+++ /dev/null
@@ -1,56 +0,0 @@
-version: '3.8'
-
-services:
- redis_db:
- extends:
- file: ./services/docker-compose-redis.yml
- service: redis_db
-
- db:
- extends:
- file: ./services/docker-compose-db.yml
- service: db
-
- blockscout:
- depends_on:
- - db
- - smart-contract-verifier
- - redis_db
- image: blockscout/blockscout:${DOCKER_TAG:-latest}
- pull_policy: always
- restart: always
- stop_grace_period: 5m
- container_name: 'blockscout'
- links:
- - db:database
- command: bash -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start"
- extra_hosts:
- - 'host.docker.internal:host-gateway'
- env_file:
- - ./envs/common-blockscout.env
- environment:
- ETHEREUM_JSONRPC_VARIANT: 'nethermind'
- ETHEREUM_JSONRPC_HTTP_URL: http://host.docker.internal:8545/
- ETHEREUM_JSONRPC_TRACE_URL: http://host.docker.internal:8545/
- DATABASE_URL: postgresql://postgres:@host.docker.internal:7432/blockscout?ssl=false
- ECTO_USE_SSL: 'false'
- SECRET_KEY_BASE: '56NtB48ear7+wMSf0IQuWDAAazhpb31qyc7GiyspBP2vh7t5zlCsF5QDv76chXeN'
- ports:
- - 4000:4000
- volumes:
- - ./logs/:/app/logs/
-
- smart-contract-verifier:
- extends:
- file: ./services/docker-compose-smart-contract-verifier.yml
- service: smart-contract-verifier
-
- visualizer:
- extends:
- file: ./services/docker-compose-visualizer.yml
- service: visualizer
-
- sig-provider:
- extends:
- file: ./services/docker-compose-sig-provider.yml
- service: sig-provider
diff --git a/docker-compose/docker-compose-no-build-no-db-container.yml b/docker-compose/docker-compose-no-build-no-db-container.yml
deleted file mode 100644
index 3ed38a111b19..000000000000
--- a/docker-compose/docker-compose-no-build-no-db-container.yml
+++ /dev/null
@@ -1,47 +0,0 @@
-version: '3.8'
-
-services:
- redis_db:
- extends:
- file: ./services/docker-compose-redis.yml
- service: redis_db
-
- blockscout:
- depends_on:
- - smart-contract-verifier
- - redis_db
- image: blockscout/blockscout:${DOCKER_TAG:-latest}
- pull_policy: always
- restart: always
- stop_grace_period: 5m
- container_name: 'blockscout'
- command: bash -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start"
- extra_hosts:
- - 'host.docker.internal:host-gateway'
- env_file:
- - ./envs/common-blockscout.env
- environment:
- ETHEREUM_JSONRPC_VARIANT: 'geth'
- ETHEREUM_JSONRPC_HTTP_URL: http://host.docker.internal:8545/
- DATABASE_URL: postgresql://postgres:@host.docker.internal:5432/blockscout?ssl=false
- ECTO_USE_SSL: 'false'
- SECRET_KEY_BASE: '56NtB48ear7+wMSf0IQuWDAAazhpb31qyc7GiyspBP2vh7t5zlCsF5QDv76chXeN'
- ports:
- - 4000:4000
- volumes:
- - ./logs/:/app/logs/
-
- smart-contract-verifier:
- extends:
- file: ./services/docker-compose-smart-contract-verifier.yml
- service: smart-contract-verifier
-
- visualizer:
- extends:
- file: ./services/docker-compose-visualizer.yml
- service: visualizer
-
- sig-provider:
- extends:
- file: ./services/docker-compose-sig-provider.yml
- service: sig-provider
diff --git a/docker-compose/docker-compose-no-rust-services.yml b/docker-compose/docker-compose-no-rust-services.yml
deleted file mode 100644
index c12212bcddb8..000000000000
--- a/docker-compose/docker-compose-no-rust-services.yml
+++ /dev/null
@@ -1,48 +0,0 @@
-version: '3.8'
-
-services:
- redis_db:
- extends:
- file: ./services/docker-compose-redis.yml
- service: redis_db
-
- db:
- extends:
- file: ./services/docker-compose-db.yml
- service: db
-
- blockscout:
- depends_on:
- - db
- - redis_db
- image: blockscout/blockscout:${DOCKER_TAG:-latest}
- build:
- context: ..
- dockerfile: ./docker/Dockerfile
- args:
- CACHE_EXCHANGE_RATES_PERIOD: ""
- API_V1_READ_METHODS_DISABLED: "false"
- DISABLE_WEBAPP: "false"
- API_V1_WRITE_METHODS_DISABLED: "false"
- CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED: ""
- CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL: ""
- ADMIN_PANEL_ENABLED: ""
- RELEASE_VERSION: 5.1.5
- restart: always
- stop_grace_period: 5m
- container_name: 'blockscout'
- links:
- - db:database
- command: bash -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start"
- extra_hosts:
- - 'host.docker.internal:host-gateway'
- env_file:
- - ./envs/common-blockscout.env
- environment:
- MICROSERVICE_SC_VERIFIER_ENABLED: 'false'
- MICROSERVICE_VISUALIZE_SOL2UML_ENABLED: 'false'
- MICROSERVICE_SIG_PROVIDER_ENABLED: 'false'
- ports:
- - 4000:4000
- volumes:
- - ./logs/:/app/logs/
diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml
index fbd08f431f4e..b00502a5a187 100644
--- a/docker-compose/docker-compose.yml
+++ b/docker-compose/docker-compose.yml
@@ -1,22 +1,28 @@
-version: '3.8'
+version: '3.9'
services:
redis_db:
extends:
- file: ./services/docker-compose-redis.yml
+ file: ./services/redis.yml
service: redis_db
+ db-init:
+ extends:
+ file: ./services/db.yml
+ service: db-init
+
db:
extends:
- file: ./services/docker-compose-db.yml
+ file: ./services/db.yml
service: db
- blockscout:
+ backend:
depends_on:
- db
- - smart-contract-verifier
- redis_db
- image: blockscout/blockscout:${DOCKER_TAG:-latest}
+ extends:
+ file: ./services/backend.yml
+ service: backend
build:
context: ..
dockerfile: ./docker/Dockerfile
@@ -28,33 +34,56 @@ services:
CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED: ""
CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL: ""
ADMIN_PANEL_ENABLED: ""
- RELEASE_VERSION: 5.1.5
- restart: always
- stop_grace_period: 5m
- container_name: 'blockscout'
+ RELEASE_VERSION: 6.0.0
links:
- db:database
- command: bash -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start"
- extra_hosts:
- - 'host.docker.internal:host-gateway'
- env_file:
- - ./envs/common-blockscout.env
- ports:
- - 4000:4000
- volumes:
- - ./logs/:/app/logs/
-
- smart-contract-verifier:
- extends:
- file: ./services/docker-compose-smart-contract-verifier.yml
- service: smart-contract-verifier
+ environment:
+ ETHEREUM_JSONRPC_HTTP_URL: http://host.docker.internal:8545/
+ ETHEREUM_JSONRPC_TRACE_URL: http://host.docker.internal:8545/
+ ETHEREUM_JSONRPC_WS_URL: ws://host.docker.internal:8545/
+ CHAIN_ID: '1337'
visualizer:
extends:
- file: ./services/docker-compose-visualizer.yml
+ file: ./services/visualizer.yml
service: visualizer
sig-provider:
extends:
- file: ./services/docker-compose-sig-provider.yml
+ file: ./services/sig-provider.yml
service: sig-provider
+
+ frontend:
+ depends_on:
+ - backend
+ extends:
+ file: ./services/frontend.yml
+ service: frontend
+
+ stats-db-init:
+ extends:
+ file: ./services/stats.yml
+ service: stats-db-init
+
+ stats-db:
+ depends_on:
+ - backend
+ extends:
+ file: ./services/stats.yml
+ service: stats-db
+
+ stats:
+ depends_on:
+ - stats-db
+ extends:
+ file: ./services/stats.yml
+ service: stats
+
+ proxy:
+ depends_on:
+ - backend
+ - frontend
+ - stats
+ extends:
+ file: ./services/nginx.yml
+ service: proxy
diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env
index 5f67ecace98f..b12e37cac135 100644
--- a/docker-compose/envs/common-blockscout.env
+++ b/docker-compose/envs/common-blockscout.env
@@ -1,22 +1,28 @@
-# DOCKER_TAG=
ETHEREUM_JSONRPC_VARIANT=geth
ETHEREUM_JSONRPC_HTTP_URL=http://host.docker.internal:8545/
# ETHEREUM_JSONRPC_FALLBACK_HTTP_URL=
-DATABASE_URL=postgresql://postgres:@host.docker.internal:7432/blockscout?ssl=false
+DATABASE_URL=postgresql://blockscout:ceWb1MeLBEeOIfk65gU8EjF8@db:5432/blockscout
+# DATABASE_QUEUE_TARGET
ETHEREUM_JSONRPC_TRACE_URL=http://host.docker.internal:8545/
# ETHEREUM_JSONRPC_FALLBACK_TRACE_URL=
+# ETHEREUM_JSONRPC_ETH_CALL_URL=
# ETHEREUM_JSONRPC_HTTP_TIMEOUT=
+# CHAIN_TYPE=
NETWORK=
SUBNETWORK=Awesome chain
LOGO=/images/blockscout_logo.svg
# ETHEREUM_JSONRPC_WS_URL=
ETHEREUM_JSONRPC_TRANSPORT=http
ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES=false
+#ETHEREUM_JSONRPC_ARCHIVE_BALANCES_WINDOW=200
+# ETHEREUM_JSONRPC_HTTP_HEADERS=
+# ETHEREUM_JSONRPC_WAIT_PER_TIMEOUT=
+# ETHEREUM_JSONRPC_GETH_TRACE_BY_BLOCK=
IPC_PATH=
NETWORK_PATH=/
BLOCKSCOUT_HOST=
BLOCKSCOUT_PROTOCOL=
-# SECRET_KEY_BASE=
+SECRET_KEY_BASE=56NtB48ear7+wMSf0IQuWDAAazhpb31qyc7GiyspBP2vh7t5zlCsF5QDv76chXeN
# CHECK_ORIGIN=
PORT=4000
COIN_NAME=
@@ -30,11 +36,14 @@ EMISSION_FORMAT=DEFAULT
# SUPPLY_MODULE=
COIN=
EXCHANGE_RATES_COIN=
-# EXCHANGE_RATES_SOURCE=
+# EXCHANGE_RATES_MARKET_CAP_SOURCE=
+# EXCHANGE_RATES_TVL_SOURCE=
+# EXCHANGE_RATES_PRICE_SOURCE=
# EXCHANGE_RATES_COINGECKO_COIN_ID=
# EXCHANGE_RATES_COINGECKO_API_KEY=
# EXCHANGE_RATES_COINMARKETCAP_API_KEY=
-POOL_SIZE=90
+# EXCHANGE_RATES_COINMARKETCAP_COIN_ID=
+POOL_SIZE=80
# EXCHANGE_RATES_COINGECKO_PLATFORM_ID=
# TOKEN_EXCHANGE_RATE_INTERVAL=
# TOKEN_EXCHANGE_RATE_REFETCH_INTERVAL=
@@ -55,6 +64,7 @@ BLOCK_TRANSFORMER=base
# BLOCK_RANGES=
# FIRST_BLOCK=
# LAST_BLOCK=
+# TRACE_BLOCK_RANGES=
# TRACE_FIRST_BLOCK=
# TRACE_LAST_BLOCK=
# FOOTER_CHAT_LINK=
@@ -81,46 +91,96 @@ CACHE_ADDRESS_TRANSACTIONS_COUNTER_PERIOD=1800
CACHE_ADDRESS_TOKENS_USD_SUM_PERIOD=3600
CACHE_ADDRESS_TOKEN_TRANSFERS_COUNTER_PERIOD=1800
TOKEN_METADATA_UPDATE_INTERVAL=172800
-CONTRACT_VERIFICATION_ALLOWED_EVM_VERSIONS=homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,berlin,london,paris,default
+CONTRACT_VERIFICATION_ALLOWED_SOLIDITY_EVM_VERSIONS=homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,berlin,london,paris,shanghai,default
+CONTRACT_VERIFICATION_ALLOWED_VYPER_EVM_VERSIONS=byzantium,constantinople,petersburg,istanbul,berlin,paris,shanghai,default
# CONTRACT_VERIFICATION_MAX_LIBRARIES=10
CONTRACT_MAX_STRING_LENGTH_WITHOUT_TRIMMING=2040
# CONTRACT_DISABLE_INTERACTION=
UNCLES_IN_AVERAGE_BLOCK_TIME=false
DISABLE_WEBAPP=false
+API_V2_ENABLED=true
API_V1_READ_METHODS_DISABLED=false
API_V1_WRITE_METHODS_DISABLED=false
+#API_RATE_LIMIT_DISABLED=true
+# API_SENSITIVE_ENDPOINTS_KEY=
+API_RATE_LIMIT_TIME_INTERVAL=1s
+API_RATE_LIMIT_BY_IP_TIME_INTERVAL=5m
+API_RATE_LIMIT=50
+API_RATE_LIMIT_BY_KEY=50
+API_RATE_LIMIT_BY_WHITELISTED_IP=50
+API_RATE_LIMIT_WHITELISTED_IPS=
+API_RATE_LIMIT_STATIC_API_KEY=
+API_RATE_LIMIT_UI_V2_WITH_TOKEN=5
+API_RATE_LIMIT_BY_IP=3000
DISABLE_INDEXER=false
DISABLE_REALTIME_INDEXER=false
+DISABLE_CATCHUP_INDEXER=false
INDEXER_DISABLE_TOKEN_INSTANCE_REALTIME_FETCHER=false
INDEXER_DISABLE_TOKEN_INSTANCE_RETRY_FETCHER=false
INDEXER_DISABLE_TOKEN_INSTANCE_SANITIZE_FETCHER=false
+INDEXER_DISABLE_TOKEN_INSTANCE_LEGACY_SANITIZE_FETCHER=false
INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER=false
INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false
# INDEXER_CATCHUP_BLOCKS_BATCH_SIZE=
# INDEXER_CATCHUP_BLOCKS_CONCURRENCY=
# INDEXER_CATCHUP_BLOCK_INTERVAL=
+# INDEXER_EMPTY_BLOCKS_SANITIZER_INTERVAL=
# INDEXER_INTERNAL_TRANSACTIONS_BATCH_SIZE=
# INDEXER_INTERNAL_TRANSACTIONS_CONCURRENCY=
# INDEXER_BLOCK_REWARD_BATCH_SIZE=
# INDEXER_BLOCK_REWARD_CONCURRENCY=
# INDEXER_TOKEN_INSTANCE_RETRY_REFETCH_INTERVAL=
+# INDEXER_TOKEN_INSTANCE_RETRY_BATCH_SIZE=10
# INDEXER_TOKEN_INSTANCE_RETRY_CONCURRENCY=
+# INDEXER_TOKEN_INSTANCE_REALTIME_BATCH_SIZE=1
# INDEXER_TOKEN_INSTANCE_REALTIME_CONCURRENCY=
+# INDEXER_TOKEN_INSTANCE_SANITIZE_BATCH_SIZE=10
# INDEXER_TOKEN_INSTANCE_SANITIZE_CONCURRENCY=
+# INDEXER_TOKEN_INSTANCE_LEGACY_SANITIZE_BATCH_SIZE=10
+# INDEXER_TOKEN_INSTANCE_LEGACY_SANITIZE_CONCURRENCY=10
+# TOKEN_INSTANCE_OWNER_MIGRATION_CONCURRENCY=5
+# TOKEN_INSTANCE_OWNER_MIGRATION_BATCH_SIZE=50
# INDEXER_COIN_BALANCES_BATCH_SIZE=
# INDEXER_COIN_BALANCES_CONCURRENCY=
# INDEXER_RECEIPTS_BATCH_SIZE=
# INDEXER_RECEIPTS_CONCURRENCY=
+# INDEXER_TOKEN_CONCURRENCY=
# INDEXER_TOKEN_BALANCES_BATCH_SIZE=
+# INDEXER_TOKEN_BALANCES_CONCURRENCY=
# INDEXER_TX_ACTIONS_ENABLE=
# INDEXER_TX_ACTIONS_MAX_TOKEN_CACHE_SIZE=
# INDEXER_TX_ACTIONS_REINDEX_FIRST_BLOCK=
# INDEXER_TX_ACTIONS_REINDEX_LAST_BLOCK=
# INDEXER_TX_ACTIONS_REINDEX_PROTOCOLS=
# INDEXER_TX_ACTIONS_AAVE_V3_POOL_CONTRACT=
+# INDEXER_POLYGON_EDGE_L1_RPC=
+# INDEXER_POLYGON_EDGE_L1_EXIT_HELPER_CONTRACT=
+# INDEXER_POLYGON_EDGE_L1_WITHDRAWALS_START_BLOCK=
+# INDEXER_POLYGON_EDGE_L1_STATE_SENDER_CONTRACT=
+# INDEXER_POLYGON_EDGE_L1_DEPOSITS_START_BLOCK=
+# INDEXER_POLYGON_EDGE_L2_STATE_SENDER_CONTRACT=
+# INDEXER_POLYGON_EDGE_L2_WITHDRAWALS_START_BLOCK=
+# INDEXER_POLYGON_EDGE_L2_STATE_RECEIVER_CONTRACT=
+# INDEXER_POLYGON_EDGE_L2_DEPOSITS_START_BLOCK=
+# INDEXER_POLYGON_EDGE_ETH_GET_LOGS_RANGE_SIZE=
+# INDEXER_ZKEVM_BATCHES_ENABLED=
+# INDEXER_ZKEVM_BATCHES_CHUNK_SIZE=
+# INDEXER_ZKEVM_BATCHES_RECHECK_INTERVAL=
# INDEXER_REALTIME_FETCHER_MAX_GAP=
+# INDEXER_FETCHER_INIT_QUERY_LIMIT=
+# INDEXER_TOKEN_BALANCES_FETCHER_INIT_QUERY_LIMIT=
+# INDEXER_COIN_BALANCES_FETCHER_INIT_QUERY_LIMIT=
# INDEXER_DISABLE_WITHDRAWALS_FETCHER=
# WITHDRAWALS_FIRST_BLOCK=
+# ROOTSTOCK_REMASC_ADDRESS=
+# ROOTSTOCK_BRIDGE_ADDRESS=
+# ROOTSTOCK_LOCKED_BTC_CACHE_PERIOD=
+# ROOTSTOCK_LOCKING_CAP=
+# INDEXER_DISABLE_ROOTSTOCK_DATA_FETCHER=
+# INDEXER_ROOTSTOCK_DATA_FETCHER_INTERVAL=
+# INDEXER_ROOTSTOCK_DATA_FETCHER_BATCH_SIZE=
+# INDEXER_ROOTSTOCK_DATA_FETCHER_CONCURRENCY=
+# INDEXER_ROOTSTOCK_DATA_FETCHER_DB_BATCH_SIZE=
# TOKEN_ID_MIGRATION_FIRST_BLOCK=
# TOKEN_ID_MIGRATION_CONCURRENCY=
# TOKEN_ID_MIGRATION_BATCH_SIZE=
@@ -141,47 +201,43 @@ COIN_BALANCE_HISTORY_DAYS=90
APPS_MENU=true
EXTERNAL_APPS=[]
# GAS_PRICE=
+# GAS_PRICE_ORACLE_CACHE_PERIOD=
+# GAS_PRICE_ORACLE_SIMPLE_TRANSACTION_GAS=
+# GAS_PRICE_ORACLE_NUM_OF_BLOCKS=
+# GAS_PRICE_ORACLE_SAFELOW_PERCENTILE=
+# GAS_PRICE_ORACLE_AVERAGE_PERCENTILE=
+# GAS_PRICE_ORACLE_FAST_PERCENTILE=
# RESTRICTED_LIST=
# RESTRICTED_LIST_KEY=
SHOW_MAINTENANCE_ALERT=false
MAINTENANCE_ALERT_MESSAGE=
-SOURCIFY_INTEGRATION_ENABLED=false
-SOURCIFY_SERVER_URL=
-SOURCIFY_REPO_URL=
CHAIN_ID=
MAX_SIZE_UNLESS_HIDE_ARRAY=50
HIDE_BLOCK_MINER=false
DISPLAY_TOKEN_ICONS=false
-SHOW_TENDERLY_LINK=false
-TENDERLY_CHAIN_PATH=
RE_CAPTCHA_SECRET_KEY=
RE_CAPTCHA_CLIENT_KEY=
RE_CAPTCHA_V3_SECRET_KEY=
RE_CAPTCHA_V3_CLIENT_KEY=
RE_CAPTCHA_DISABLED=false
JSON_RPC=
-#API_RATE_LIMIT_DISABLED=true
-API_RATE_LIMIT_TIME_INTERVAL=1s
-API_RATE_LIMIT_BY_IP_TIME_INTERVAL=5m
-API_RATE_LIMIT=50
-API_RATE_LIMIT_BY_KEY=50
-API_RATE_LIMIT_BY_WHITELISTED_IP=50
-API_RATE_LIMIT_WHITELISTED_IPS=
-API_RATE_LIMIT_STATIC_API_KEY=
-API_RATE_LIMIT_UI_V2_WITH_TOKEN=5
-API_RATE_LIMIT_BY_IP=3000
# API_RATE_LIMIT_HAMMER_REDIS_URL=redis://redis_db:6379/1
# API_RATE_LIMIT_IS_BLOCKSCOUT_BEHIND_PROXY=false
API_RATE_LIMIT_UI_V2_TOKEN_TTL_IN_SECONDS=18000
FETCH_REWARDS_WAY=trace_block
MICROSERVICE_SC_VERIFIER_ENABLED=true
-MICROSERVICE_SC_VERIFIER_URL=http://smart-contract-verifier:8050/
-MICROSERVICE_SC_VERIFIER_TYPE=sc_verifier
+#MICROSERVICE_SC_VERIFIER_URL=http://smart-contract-verifier:8050/
+#MICROSERVICE_SC_VERIFIER_TYPE=sc_verifier
+MICROSERVICE_SC_VERIFIER_URL=https://eth-bytecode-db.services.blockscout.com/
+MICROSERVICE_SC_VERIFIER_TYPE=eth_bytecode_db
MICROSERVICE_ETH_BYTECODE_DB_INTERVAL_BETWEEN_LOOKUPS=10m
+MICROSERVICE_ETH_BYTECODE_DB_MAX_LOOKUPS_CONCURRENCY=10
MICROSERVICE_VISUALIZE_SOL2UML_ENABLED=true
MICROSERVICE_VISUALIZE_SOL2UML_URL=http://visualizer:8050/
MICROSERVICE_SIG_PROVIDER_ENABLED=true
MICROSERVICE_SIG_PROVIDER_URL=http://sig-provider:8050/
+# MICROSERVICE_BENS_URL=
+# MICROSERVICE_BENS_ENABLED=
DECODE_NOT_A_CONTRACT_CALLS=true
# DATABASE_READ_ONLY_API_URL=
# ACCOUNT_DATABASE_URL=
@@ -194,13 +250,28 @@ DECODE_NOT_A_CONTRACT_CALLS=true
# ACCOUNT_SENDGRID_API_KEY=
# ACCOUNT_SENDGRID_SENDER=
# ACCOUNT_SENDGRID_TEMPLATE=
+# ACCOUNT_VERIFICATION_EMAIL_RESEND_INTERVAL=
+# ACCOUNT_PRIVATE_TAGS_LIMIT=2000
+# ACCOUNT_WATCHLIST_ADDRESSES_LIMIT=15
ACCOUNT_CLOAK_KEY=
ACCOUNT_ENABLED=false
ACCOUNT_REDIS_URL=redis://redis_db:6379
+EIP_1559_ELASTICITY_MULTIPLIER=2
# MIXPANEL_TOKEN=
# MIXPANEL_URL=
# AMPLITUDE_API_KEY=
# AMPLITUDE_URL=
-EIP_1559_ELASTICITY_MULTIPLIER=2
-# API_SENSITIVE_ENDPOINTS_KEY=
-# ACCOUNT_VERIFICATION_EMAIL_RESEND_INTERVAL=
+# IPFS_GATEWAY_URL=
+# ADDRESSES_TABS_COUNTERS_TTL=10m
+# DENORMALIZATION_MIGRATION_BATCH_SIZE=
+# DENORMALIZATION_MIGRATION_CONCURRENCY=
+SOURCIFY_INTEGRATION_ENABLED=false
+SOURCIFY_SERVER_URL=
+SOURCIFY_REPO_URL=
+SHOW_TENDERLY_LINK=false
+TENDERLY_CHAIN_PATH=
+# SOLIDITYSCAN_CHAIN_ID=
+# SOLIDITYSCAN_API_TOKEN=
+# NOVES_FI_BASE_API_URL=
+# NOVES_FI_CHAIN_NAME=
+# NOVES_FI_API_TOKEN=
diff --git a/docker-compose/envs/common-frontend.env b/docker-compose/envs/common-frontend.env
index 997c82a643d9..a3dcded7638c 100644
--- a/docker-compose/envs/common-frontend.env
+++ b/docker-compose/envs/common-frontend.env
@@ -1,26 +1,17 @@
NEXT_PUBLIC_API_HOST=localhost
-NEXT_PUBLIC_STATS_API_HOST=http://localhost:8153
-NEXT_PUBLIC_API_PORT=80
NEXT_PUBLIC_API_PROTOCOL=http
-NEXT_PUBLIC_NETWORK_NAME=Göerli
-NEXT_PUBLIC_NETWORK_SHORT_NAME=Göerli
-NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME=ethereum
+NEXT_PUBLIC_STATS_API_HOST=http://localhost:8080
+NEXT_PUBLIC_NETWORK_NAME=Awesome chain
+NEXT_PUBLIC_NETWORK_SHORT_NAME=Awesome chain
NEXT_PUBLIC_NETWORK_ID=5
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_API_BASE_PATH=/
-NEXT_PUBLIC_BLOCKSCOUT_VERSION=v5.1.5-beta
-NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/eth-goerli.json
-NEXT_PUBLIC_FOOTER_GITHUB_LINK=https://github.com/blockscout/blockscout
-NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
-NEXT_PUBLIC_APP_ENV=staging
-NEXT_PUBLIC_APP_INSTANCE=eth_goerli
NEXT_PUBLIC_APP_HOST=localhost
-NEXT_PUBLIC_HOMEPAGE_CHARTS="['daily_txs']"
-NEXT_PUBLIC_VISUALIZE_API_HOST=http://visualizer:80
-NEXT_PUBLIC_IS_TESTNET='true'
-NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/goerli.svg
-NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/goerli.svg
-NEXT_PUBLIC_HOMEPAGE_PLATE_GRADIENT="radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%)"
-NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR="rgb(255, 255, 255)"
+NEXT_PUBLIC_APP_PROTOCOL=http
+NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
+NEXT_PUBLIC_VISUALIZE_API_HOST=http://localhost:8081
+NEXT_PUBLIC_IS_TESTNET=true
+NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
+NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
\ No newline at end of file
diff --git a/docker-compose/envs/common-stats.env b/docker-compose/envs/common-stats.env
index aa1f48225e6b..f5eed636bab6 100644
--- a/docker-compose/envs/common-stats.env
+++ b/docker-compose/envs/common-stats.env
@@ -15,7 +15,6 @@ STATS__CREATE_DATABASE=false
STATS__RUN_MIGRATIONS=false
STATS__DEFAULT_SCHEDULE=0 0 1 * * * *
STATS__FORCE_UPDATE_ON_START=false
-STATS__CHARTS_CONFIG=config/charts.toml
STATS__METRICS__ENABLED=false
STATS__METRICS__ADDR=0.0.0.0:6060
diff --git a/docker-compose/erigon.yml b/docker-compose/erigon.yml
new file mode 100644
index 000000000000..0dbcef7f3e0e
--- /dev/null
+++ b/docker-compose/erigon.yml
@@ -0,0 +1,74 @@
+version: '3.9'
+
+services:
+ redis_db:
+ extends:
+ file: ./services/redis.yml
+ service: redis_db
+
+ db-init:
+ extends:
+ file: ./services/db.yml
+ service: db-init
+
+ db:
+ extends:
+ file: ./services/db.yml
+ service: db
+
+ backend:
+ depends_on:
+ - db
+ - redis_db
+ extends:
+ file: ./services/backend.yml
+ service: backend
+ links:
+ - db:database
+ environment:
+ ETHEREUM_JSONRPC_VARIANT: 'erigon'
+
+ visualizer:
+ extends:
+ file: ./services/visualizer.yml
+ service: visualizer
+
+ sig-provider:
+ extends:
+ file: ./services/sig-provider.yml
+ service: sig-provider
+
+ frontend:
+ depends_on:
+ - backend
+ extends:
+ file: ./services/frontend.yml
+ service: frontend
+
+ stats-db-init:
+ extends:
+ file: ./services/stats.yml
+ service: stats-db-init
+
+ stats-db:
+ depends_on:
+ - backend
+ extends:
+ file: ./services/stats.yml
+ service: stats-db
+
+ stats:
+ depends_on:
+ - stats-db
+ extends:
+ file: ./services/stats.yml
+ service: stats
+
+ proxy:
+ depends_on:
+ - backend
+ - frontend
+ - stats
+ extends:
+ file: ./services/nginx.yml
+ service: proxy
diff --git a/docker-compose/external-backend.yml b/docker-compose/external-backend.yml
new file mode 100644
index 000000000000..db8df3758037
--- /dev/null
+++ b/docker-compose/external-backend.yml
@@ -0,0 +1,57 @@
+version: '3.9'
+
+services:
+ redis_db:
+ extends:
+ file: ./services/redis.yml
+ service: redis_db
+
+ db-init:
+ extends:
+ file: ./services/db.yml
+ service: db-init
+
+ db:
+ extends:
+ file: ./services/db.yml
+ service: db
+
+ visualizer:
+ extends:
+ file: ./services/visualizer.yml
+ service: visualizer
+
+ sig-provider:
+ extends:
+ file: ./services/sig-provider.yml
+ service: sig-provider
+
+ frontend:
+ extends:
+ file: ./services/frontend.yml
+ service: frontend
+
+ stats-db-init:
+ extends:
+ file: ./services/stats.yml
+ service: stats-db-init
+
+ stats-db:
+ extends:
+ file: ./services/stats.yml
+ service: stats-db
+
+ stats:
+ depends_on:
+ - stats-db
+ extends:
+ file: ./services/stats.yml
+ service: stats
+
+ proxy:
+ depends_on:
+ - frontend
+ - stats
+ extends:
+ file: ./services/nginx.yml
+ service: proxy
diff --git a/docker-compose/external-db.yml b/docker-compose/external-db.yml
new file mode 100644
index 000000000000..b40151c51dec
--- /dev/null
+++ b/docker-compose/external-db.yml
@@ -0,0 +1,61 @@
+version: '3.9'
+
+services:
+ redis_db:
+ extends:
+ file: ./services/redis.yml
+ service: redis_db
+
+ backend:
+ depends_on:
+ - redis_db
+ extends:
+ file: ./services/backend.yml
+ service: backend
+ environment:
+ ETHEREUM_JSONRPC_VARIANT: 'geth'
+
+ visualizer:
+ extends:
+ file: ./services/visualizer.yml
+ service: visualizer
+
+ sig-provider:
+ extends:
+ file: ./services/sig-provider.yml
+ service: sig-provider
+
+ frontend:
+ depends_on:
+ - backend
+ extends:
+ file: ./services/frontend.yml
+ service: frontend
+
+ stats-db-init:
+ extends:
+ file: ./services/stats.yml
+ service: stats-db-init
+
+ stats-db:
+ depends_on:
+ - backend
+ extends:
+ file: ./services/stats.yml
+ service: stats-db
+
+ stats:
+ depends_on:
+ - stats-db
+ extends:
+ file: ./services/stats.yml
+ service: stats
+
+ proxy:
+ depends_on:
+ - backend
+ - frontend
+ - stats
+ extends:
+ file: ./services/nginx.yml
+ service: proxy
diff --git a/docker-compose/external-frontend.yml b/docker-compose/external-frontend.yml
new file mode 100644
index 000000000000..f0d9f9f89298
--- /dev/null
+++ b/docker-compose/external-frontend.yml
@@ -0,0 +1,70 @@
+version: '3.9'
+
+services:
+ redis_db:
+ extends:
+ file: ./services/redis.yml
+ service: redis_db
+
+ db-init:
+ extends:
+ file: ./services/db.yml
+ service: db-init
+
+ db:
+ extends:
+ file: ./services/db.yml
+ service: db
+
+ backend:
+ depends_on:
+ - db
+ - redis_db
+ extends:
+ file: ./services/backend.yml
+ service: backend
+ links:
+ - db:database
+ environment:
+ ETHEREUM_JSONRPC_VARIANT: 'ganache'
+ ETHEREUM_JSONRPC_WS_URL: ws://host.docker.internal:8545/
+ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER: 'true'
+ INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER: 'true'
+ CHAIN_ID: '1337'
+
+ visualizer:
+ extends:
+ file: ./services/visualizer.yml
+ service: visualizer
+
+ sig-provider:
+ extends:
+ file: ./services/sig-provider.yml
+ service: sig-provider
+
+ stats-db-init:
+ extends:
+ file: ./services/stats.yml
+ service: stats-db-init
+
+ stats-db:
+ depends_on:
+ - backend
+ extends:
+ file: ./services/stats.yml
+ service: stats-db
+
+ stats:
+ depends_on:
+ - stats-db
+ extends:
+ file: ./services/stats.yml
+ service: stats
+
+ proxy:
+ depends_on:
+ - backend
+ - stats
+ extends:
+ file: ./services/nginx.yml
+ service: proxy
\ No newline at end of file
diff --git a/docker-compose/ganache.yml b/docker-compose/ganache.yml
new file mode 100644
index 000000000000..0aa51fce16a2
--- /dev/null
+++ b/docker-compose/ganache.yml
@@ -0,0 +1,78 @@
+version: '3.9'
+
+services:
+ redis_db:
+ extends:
+ file: ./services/redis.yml
+ service: redis_db
+
+ db-init:
+ extends:
+ file: ./services/db.yml
+ service: db-init
+
+ db:
+ extends:
+ file: ./services/db.yml
+ service: db
+
+ backend:
+ depends_on:
+ - db
+ - redis_db
+ extends:
+ file: ./services/backend.yml
+ service: backend
+ links:
+ - db:database
+ environment:
+ ETHEREUM_JSONRPC_VARIANT: 'ganache'
+ ETHEREUM_JSONRPC_WS_URL: ws://host.docker.internal:8545/
+ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER: 'true'
+ INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER: 'true'
+ CHAIN_ID: '1337'
+
+ visualizer:
+ extends:
+ file: ./services/visualizer.yml
+ service: visualizer
+
+ sig-provider:
+ extends:
+ file: ./services/sig-provider.yml
+ service: sig-provider
+
+ frontend:
+ depends_on:
+ - backend
+ extends:
+ file: ./services/frontend.yml
+ service: frontend
+
+ stats-db-init:
+ extends:
+ file: ./services/stats.yml
+ service: stats-db-init
+
+ stats-db:
+ depends_on:
+ - backend
+ extends:
+ file: ./services/stats.yml
+ service: stats-db
+
+ stats:
+ depends_on:
+ - stats-db
+ extends:
+ file: ./services/stats.yml
+ service: stats
+
+ proxy:
+ depends_on:
+ - backend
+ - frontend
+ - stats
+ extends:
+ file: ./services/nginx.yml
+ service: proxy
diff --git a/docker-compose/geth-clique-consensus.yml b/docker-compose/geth-clique-consensus.yml
new file mode 100644
index 000000000000..ac1573849204
--- /dev/null
+++ b/docker-compose/geth-clique-consensus.yml
@@ -0,0 +1,75 @@
+version: '3.9'
+
+services:
+ redis_db:
+ extends:
+ file: ./services/redis.yml
+ service: redis_db
+
+ db-init:
+ extends:
+ file: ./services/db.yml
+ service: db-init
+
+ db:
+ extends:
+ file: ./services/db.yml
+ service: db
+
+ backend:
+ depends_on:
+ - db
+ - redis_db
+ extends:
+ file: ./services/backend.yml
+ service: backend
+ links:
+ - db:database
+ environment:
+ ETHEREUM_JSONRPC_VARIANT: 'geth'
+ BLOCK_TRANSFORMER: 'clique'
+
+ visualizer:
+ extends:
+ file: ./services/visualizer.yml
+ service: visualizer
+
+ sig-provider:
+ extends:
+ file: ./services/sig-provider.yml
+ service: sig-provider
+
+ frontend:
+ depends_on:
+ - backend
+ extends:
+ file: ./services/frontend.yml
+ service: frontend
+
+ stats-db-init:
+ extends:
+ file: ./services/stats.yml
+ service: stats-db-init
+
+ stats-db:
+ depends_on:
+ - backend
+ extends:
+ file: ./services/stats.yml
+ service: stats-db
+
+ stats:
+ depends_on:
+ - stats-db
+ extends:
+ file: ./services/stats.yml
+ service: stats
+
+ proxy:
+ depends_on:
+ - backend
+ - frontend
+ - stats
+ extends:
+ file: ./services/nginx.yml
+ service: proxy
diff --git a/docker-compose/geth.yml b/docker-compose/geth.yml
new file mode 100644
index 000000000000..611acec30606
--- /dev/null
+++ b/docker-compose/geth.yml
@@ -0,0 +1,74 @@
+version: '3.9'
+
+services:
+ redis_db:
+ extends:
+ file: ./services/redis.yml
+ service: redis_db
+
+ db-init:
+ extends:
+ file: ./services/db.yml
+ service: db-init
+
+ db:
+ extends:
+ file: ./services/db.yml
+ service: db
+
+ backend:
+ depends_on:
+ - db
+ - redis_db
+ extends:
+ file: ./services/backend.yml
+ service: backend
+ links:
+ - db:database
+ environment:
+ ETHEREUM_JSONRPC_VARIANT: 'geth'
+
+ visualizer:
+ extends:
+ file: ./services/visualizer.yml
+ service: visualizer
+
+ sig-provider:
+ extends:
+ file: ./services/sig-provider.yml
+ service: sig-provider
+
+ frontend:
+ depends_on:
+ - backend
+ extends:
+ file: ./services/frontend.yml
+ service: frontend
+
+ stats-db-init:
+ extends:
+ file: ./services/stats.yml
+ service: stats-db-init
+
+ stats-db:
+ depends_on:
+ - backend
+ extends:
+ file: ./services/stats.yml
+ service: stats-db
+
+ stats:
+ depends_on:
+ - stats-db
+ extends:
+ file: ./services/stats.yml
+ service: stats
+
+ proxy:
+ depends_on:
+ - backend
+ - frontend
+ - stats
+ extends:
+ file: ./services/nginx.yml
+ service: proxy
diff --git a/docker-compose/hardhat-network.yml b/docker-compose/hardhat-network.yml
new file mode 100644
index 000000000000..518a3b2af73b
--- /dev/null
+++ b/docker-compose/hardhat-network.yml
@@ -0,0 +1,76 @@
+version: '3.9'
+
+services:
+ redis_db:
+ extends:
+ file: ./services/redis.yml
+ service: redis_db
+
+ db-init:
+ extends:
+ file: ./services/db.yml
+ service: db-init
+
+ db:
+ extends:
+ file: ./services/db.yml
+ service: db
+
+ backend:
+ depends_on:
+ - db
+ - redis_db
+ extends:
+ file: ./services/backend.yml
+ service: backend
+ links:
+ - db:database
+ environment:
+ ETHEREUM_JSONRPC_VARIANT: 'geth'
+ ETHEREUM_JSONRPC_WS_URL: ws://host.docker.internal:8545/
+ INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER: 'true'
+
+ visualizer:
+ extends:
+ file: ./services/visualizer.yml
+ service: visualizer
+
+ sig-provider:
+ extends:
+ file: ./services/sig-provider.yml
+ service: sig-provider
+
+ frontend:
+ depends_on:
+ - backend
+ extends:
+ file: ./services/frontend.yml
+ service: frontend
+
+ stats-db-init:
+ extends:
+ file: ./services/stats.yml
+ service: stats-db-init
+
+ stats-db:
+ depends_on:
+ - backend
+ extends:
+ file: ./services/stats.yml
+ service: stats-db
+
+ stats:
+ depends_on:
+ - stats-db
+ extends:
+ file: ./services/stats.yml
+ service: stats
+
+ proxy:
+ depends_on:
+ - backend
+ - frontend
+ - stats
+ extends:
+ file: ./services/nginx.yml
+ service: proxy
diff --git a/docker-compose/microservices.yml b/docker-compose/microservices.yml
new file mode 100644
index 000000000000..ee4a07bd6d1d
--- /dev/null
+++ b/docker-compose/microservices.yml
@@ -0,0 +1,49 @@
+version: '3.9'
+
+services:
+ visualizer:
+ extends:
+ file: ./services/visualizer.yml
+ service: visualizer
+
+ sig-provider:
+ extends:
+ file: ./services/sig-provider.yml
+ service: sig-provider
+ ports:
+ - 8083:8050
+
+
+ sc-verifier:
+ extends:
+ file: ./services/smart-contract-verifier.yml
+ service: smart-contract-verifier
+ ports:
+ - 8082:8050
+
+ stats-db-init:
+ extends:
+ file: ./services/stats.yml
+ service: stats-db-init
+
+ stats-db:
+ extends:
+ file: ./services/stats.yml
+ service: stats-db
+
+ stats:
+ depends_on:
+ - stats-db
+ extends:
+ file: ./services/stats.yml
+ service: stats
+
+ proxy:
+ depends_on:
+ - visualizer
+ - stats
+ extends:
+ file: ./services/nginx.yml
+ service: proxy
+ volumes:
+ - "./proxy/microservices.conf.template:/etc/nginx/templates/default.conf.template"
diff --git a/docker-compose/proxy/default.conf.template b/docker-compose/proxy/default.conf.template
new file mode 100644
index 000000000000..dbd5180d10b1
--- /dev/null
+++ b/docker-compose/proxy/default.conf.template
@@ -0,0 +1,93 @@
+map $http_upgrade $connection_upgrade {
+
+ default upgrade;
+ '' close;
+}
+
+server {
+ listen 80;
+ server_name localhost;
+ proxy_http_version 1.1;
+
+ location ~ ^/(api|socket|sitemap.xml|auth/auth0|auth/auth0/callback|auth/logout) {
+ proxy_pass ${BACK_PROXY_PASS};
+ proxy_http_version 1.1;
+ proxy_set_header Host "$host";
+ proxy_set_header X-Real-IP "$remote_addr";
+ proxy_set_header X-Forwarded-For "$proxy_add_x_forwarded_for";
+ proxy_set_header X-Forwarded-Proto "$scheme";
+ proxy_set_header Upgrade "$http_upgrade";
+ proxy_set_header Connection $connection_upgrade;
+ proxy_cache_bypass $http_upgrade;
+ }
+ location / {
+ proxy_pass ${FRONT_PROXY_PASS};
+ proxy_http_version 1.1;
+ proxy_set_header Host "$host";
+ proxy_set_header X-Real-IP "$remote_addr";
+ proxy_set_header X-Forwarded-For "$proxy_add_x_forwarded_for";
+ proxy_set_header X-Forwarded-Proto "$scheme";
+ proxy_set_header Upgrade "$http_upgrade";
+ proxy_set_header Connection $connection_upgrade;
+ proxy_cache_bypass $http_upgrade;
+ }
+}
+server {
+ listen 8080;
+ server_name localhost;
+ proxy_http_version 1.1;
+ proxy_hide_header Access-Control-Allow-Origin;
+ proxy_hide_header Access-Control-Allow-Methods;
+ add_header 'Access-Control-Allow-Origin' 'http://localhost' always;
+ add_header 'Access-Control-Allow-Credentials' 'true' always;
+ add_header 'Access-Control-Allow-Methods' 'PUT, GET, POST, OPTIONS, DELETE, PATCH' always;
+
+ location / {
+ proxy_pass http://stats:8050/;
+ proxy_http_version 1.1;
+ proxy_set_header Host "$host";
+ proxy_set_header X-Real-IP "$remote_addr";
+ proxy_set_header X-Forwarded-For "$proxy_add_x_forwarded_for";
+ proxy_set_header X-Forwarded-Proto "$scheme";
+ proxy_set_header Upgrade "$http_upgrade";
+ proxy_set_header Connection $connection_upgrade;
+ proxy_cache_bypass $http_upgrade;
+ }
+}
+server {
+ listen 8081;
+ server_name localhost;
+ proxy_http_version 1.1;
+ proxy_hide_header Access-Control-Allow-Origin;
+ proxy_hide_header Access-Control-Allow-Methods;
+ add_header 'Access-Control-Allow-Origin' 'http://localhost' always;
+ add_header 'Access-Control-Allow-Credentials' 'true' always;
+ add_header 'Access-Control-Allow-Methods' 'PUT, GET, POST, OPTIONS, DELETE, PATCH' always;
+ add_header 'Access-Control-Allow-Headers' 'DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,x-csrf-token' always;
+
+ location / {
+ proxy_pass http://visualizer:8050/;
+ proxy_http_version 1.1;
+ proxy_buffering off;
+ proxy_set_header Host "$host";
+ proxy_set_header X-Real-IP "$remote_addr";
+ proxy_connect_timeout 30m;
+ proxy_read_timeout 30m;
+ proxy_send_timeout 30m;
+ proxy_set_header X-Forwarded-For "$proxy_add_x_forwarded_for";
+ proxy_set_header X-Forwarded-Proto "$scheme";
+ proxy_set_header Upgrade "$http_upgrade";
+ proxy_set_header Connection $connection_upgrade;
+ proxy_cache_bypass $http_upgrade;
+ if ($request_method = 'OPTIONS') {
+ add_header 'Access-Control-Allow-Origin' 'http://localhost' always;
+ add_header 'Access-Control-Allow-Credentials' 'true' always;
+ add_header 'Access-Control-Allow-Methods' 'PUT, GET, POST, OPTIONS, DELETE, PATCH' always;
+ add_header 'Access-Control-Allow-Headers' 'DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,x-csrf-token' always;
+ add_header 'Access-Control-Max-Age' 1728000;
+ add_header 'Content-Type' 'text/plain charset=UTF-8';
+ add_header 'Content-Length' 0;
+ return 204;
+ }
+ }
+}
\ No newline at end of file
diff --git a/docker-compose/proxy/microservices.conf.template b/docker-compose/proxy/microservices.conf.template
new file mode 100644
index 000000000000..708812f57113
--- /dev/null
+++ b/docker-compose/proxy/microservices.conf.template
@@ -0,0 +1,65 @@
+map $http_upgrade $connection_upgrade {
+
+ default upgrade;
+ '' close;
+}
+
+server {
+ listen 8080;
+ server_name localhost;
+ proxy_http_version 1.1;
+ proxy_hide_header Access-Control-Allow-Origin;
+ proxy_hide_header Access-Control-Allow-Methods;
+ add_header 'Access-Control-Allow-Origin' 'http://localhost:3000' always;
+ add_header 'Access-Control-Allow-Credentials' 'true' always;
+ add_header 'Access-Control-Allow-Methods' 'PUT, GET, POST, OPTIONS, DELETE, PATCH' always;
+
+ location / {
+ proxy_pass http://stats:8050/;
+ proxy_http_version 1.1;
+ proxy_set_header Host "$host";
+ proxy_set_header X-Real-IP "$remote_addr";
+ proxy_set_header X-Forwarded-For "$proxy_add_x_forwarded_for";
+ proxy_set_header X-Forwarded-Proto "$scheme";
+ proxy_set_header Upgrade "$http_upgrade";
+ proxy_set_header Connection $connection_upgrade;
+ proxy_cache_bypass $http_upgrade;
+ }
+}
+server {
+ listen 8081;
+ server_name localhost;
+ proxy_http_version 1.1;
+ proxy_hide_header Access-Control-Allow-Origin;
+ proxy_hide_header Access-Control-Allow-Methods;
+ add_header 'Access-Control-Allow-Origin' 'http://localhost:3000' always;
+ add_header 'Access-Control-Allow-Credentials' 'true' always;
+ add_header 'Access-Control-Allow-Methods' 'PUT, GET, POST, OPTIONS, DELETE, PATCH' always;
+ add_header 'Access-Control-Allow-Headers' 'DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,x-csrf-token' always;
+
+ location / {
+ proxy_pass http://visualizer:8050/;
+ proxy_http_version 1.1;
+ proxy_buffering off;
+ proxy_set_header Host "$host";
+ proxy_set_header X-Real-IP "$remote_addr";
+ proxy_connect_timeout 30m;
+ proxy_read_timeout 30m;
+ proxy_send_timeout 30m;
+ proxy_set_header X-Forwarded-For "$proxy_add_x_forwarded_for";
+ proxy_set_header X-Forwarded-Proto "$scheme";
+ proxy_set_header Upgrade "$http_upgrade";
+ proxy_set_header Connection $connection_upgrade;
+ proxy_cache_bypass $http_upgrade;
+ if ($request_method = 'OPTIONS') {
+ add_header 'Access-Control-Allow-Origin' 'http://localhost:3000' always;
+ add_header 'Access-Control-Allow-Credentials' 'true' always;
+ add_header 'Access-Control-Allow-Methods' 'PUT, GET, POST, OPTIONS, DELETE, PATCH' always;
+ add_header 'Access-Control-Allow-Headers' 'DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,x-csrf-token' always;
+ add_header 'Access-Control-Max-Age' 1728000;
+ add_header 'Content-Type' 'text/plain charset=UTF-8';
+ add_header 'Content-Length' 0;
+ return 204;
+ }
+ }
+}
\ No newline at end of file
diff --git a/docker-compose/proxy/nginx.conf b/docker-compose/proxy/nginx.conf
deleted file mode 100644
index cc5a44509e95..000000000000
--- a/docker-compose/proxy/nginx.conf
+++ /dev/null
@@ -1,14 +0,0 @@
-server {
- listen 80;
- server_name localhost;
- proxy_http_version 1.1;
- location / {
- proxy_pass http://frontend:3000;
- }
- location /api/v2 {
- proxy_pass http://backend:4000/api/v2;
- }
- location /api/account/v1 {
- proxy_pass http://backend:4000/api/account/v1;
- }
-}
\ No newline at end of file
diff --git a/docker-compose/services/backend.yml b/docker-compose/services/backend.yml
new file mode 100644
index 000000000000..cf8a0871d366
--- /dev/null
+++ b/docker-compose/services/backend.yml
@@ -0,0 +1,16 @@
+version: '3.9'
+
+services:
+ backend:
+ image: blockscout/${DOCKER_REPO:-blockscout}:${DOCKER_TAG:-latest}
+ pull_policy: always
+ restart: always
+ stop_grace_period: 5m
+ container_name: 'backend'
+ command: sh -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start"
+ extra_hosts:
+ - 'host.docker.internal:host-gateway'
+ env_file:
+ - ../envs/common-blockscout.env
+ volumes:
+ - ./logs/:/app/logs/
\ No newline at end of file
diff --git a/docker-compose/services/db.yml b/docker-compose/services/db.yml
new file mode 100644
index 000000000000..b58aba6e0e35
--- /dev/null
+++ b/docker-compose/services/db.yml
@@ -0,0 +1,37 @@
+version: '3.9'
+
+services:
+ db-init:
+ image: postgres:14
+ volumes:
+ - ./blockscout-db-data:/var/lib/postgresql/data
+ entrypoint:
+ - sh
+ - -c
+ - |
+ chown -R 2000:2000 /var/lib/postgresql/data
+
+ db:
+ depends_on:
+ db-init:
+ condition: service_completed_successfully
+ image: postgres:14
+ user: 2000:2000
+ shm_size: 256m
+ restart: always
+ container_name: 'db'
+ command: postgres -c 'max_connections=200' -c 'client_connection_check_interval=60000'
+ environment:
+ POSTGRES_DB: 'blockscout'
+ POSTGRES_USER: 'blockscout'
+ POSTGRES_PASSWORD: 'ceWb1MeLBEeOIfk65gU8EjF8'
+ ports:
+ - 7432:5432
+ volumes:
+ - ./blockscout-db-data:/var/lib/postgresql/data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U blockscout -d blockscout"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ start_period: 10s
diff --git a/docker-compose/services/docker-compose-db.yml b/docker-compose/services/docker-compose-db.yml
deleted file mode 100644
index 51a139dff4d8..000000000000
--- a/docker-compose/services/docker-compose-db.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-version: '3.8'
-
-services:
- db:
- image: postgres:14
- restart: always
- container_name: 'postgres'
- command: postgres -c 'max_connections=200'
- environment:
- POSTGRES_PASSWORD: ''
- POSTGRES_USER: 'postgres'
- POSTGRES_HOST_AUTH_METHOD: 'trust'
- ports:
- - 7432:5432
- volumes:
- - ./blockscout-db-data:/var/lib/postgresql/data/
diff --git a/docker-compose/services/docker-compose-nginx.yml b/docker-compose/services/docker-compose-nginx.yml
deleted file mode 100644
index 79ede9d9626e..000000000000
--- a/docker-compose/services/docker-compose-nginx.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-version: '3.8'
-
-services:
- proxy:
- image: nginx
- volumes:
- - type: bind
- source: ../proxy/nginx.conf
- target: /etc/nginx/conf.d/default.conf
- read_only: true
- ports:
- - 80:80
\ No newline at end of file
diff --git a/docker-compose/services/docker-compose-stats.yml b/docker-compose/services/docker-compose-stats.yml
deleted file mode 100644
index 95c847d944fb..000000000000
--- a/docker-compose/services/docker-compose-stats.yml
+++ /dev/null
@@ -1,35 +0,0 @@
-version: '3.8'
-
-services:
- stats-db:
- image: postgres:14
- restart: always
- container_name: 'stats-postgres'
- command: postgres -c 'max_connections=200'
- environment:
- POSTGRES_PASSWORD: ''
- POSTGRES_USER: 'postgres'
- POSTGRES_HOST_AUTH_METHOD: 'trust'
- ports:
- - 7433:5432
-
- stats:
- depends_on:
- - stats-db
- image: ghcr.io/blockscout/stats:${STATS_DOCKER_TAG:-latest}
- pull_policy: always
- restart: always
- container_name: 'stats'
- extra_hosts:
- - 'host.docker.internal:host-gateway'
- env_file:
- - ../envs/common-stats.env
- environment:
- - STATS__DB_URL=postgres://postgres:@stats-db:5432/stats
- - STATS__BLOCKSCOUT_DB_URL=postgresql://postgres:@host.docker.internal:7432/blockscout?ssl=false
- - STATS__CREATE_DATABASE=true
- - STATS__RUN_MIGRATIONS=true
- ports:
- - 8153:8050
- volumes:
- - ./stats-db-data:/var/lib/postgresql/data/
diff --git a/docker-compose/services/docker-compose-frontend.yml b/docker-compose/services/frontend.yml
similarity index 57%
rename from docker-compose/services/docker-compose-frontend.yml
rename to docker-compose/services/frontend.yml
index 2c1124a2482a..2dba9b2856ff 100644
--- a/docker-compose/services/docker-compose-frontend.yml
+++ b/docker-compose/services/frontend.yml
@@ -1,12 +1,11 @@
-version: '3.8'
+version: '3.9'
services:
frontend:
- image: ghcr.io/blockscout/frontend:${FRONTEND_DOCKER_TAG:-main}
+ image: ghcr.io/blockscout/frontend:${FRONTEND_DOCKER_TAG:-latest}
pull_policy: always
+ platform: linux/amd64
restart: always
container_name: 'frontend'
env_file:
- ../envs/common-frontend.env
- ports:
- - 3000:3000
diff --git a/docker-compose/services/nginx.yml b/docker-compose/services/nginx.yml
new file mode 100644
index 000000000000..27c0a0a6a662
--- /dev/null
+++ b/docker-compose/services/nginx.yml
@@ -0,0 +1,17 @@
+version: '3.9'
+
+services:
+ proxy:
+ image: nginx
+ container_name: proxy
+ extra_hosts:
+ - 'host.docker.internal:host-gateway'
+ volumes:
+ - "../proxy:/etc/nginx/templates"
+ environment:
+ BACK_PROXY_PASS: ${BACK_PROXY_PASS:-http://backend:4000}
+ FRONT_PROXY_PASS: ${FRONT_PROXY_PASS:-http://frontend:3000}
+ ports:
+ - 80:80
+ - 8080:8080
+ - 8081:8081
diff --git a/docker-compose/services/docker-compose-redis.yml b/docker-compose/services/redis.yml
similarity index 76%
rename from docker-compose/services/docker-compose-redis.yml
rename to docker-compose/services/redis.yml
index 1101d9b03e22..9760137f5bb5 100644
--- a/docker-compose/services/docker-compose-redis.yml
+++ b/docker-compose/services/redis.yml
@@ -1,10 +1,8 @@
-version: '3.8'
+version: '3.9'
services:
redis_db:
image: 'redis:alpine'
- ports:
- - 6379:6379
container_name: redis_db
command: redis-server
volumes:
diff --git a/docker-compose/services/docker-compose-sig-provider.yml b/docker-compose/services/sig-provider.yml
similarity index 80%
rename from docker-compose/services/docker-compose-sig-provider.yml
rename to docker-compose/services/sig-provider.yml
index 2a75f9fc18c4..8c0cb33337c9 100644
--- a/docker-compose/services/docker-compose-sig-provider.yml
+++ b/docker-compose/services/sig-provider.yml
@@ -1,10 +1,9 @@
-version: '3.8'
+version: '3.9'
services:
sig-provider:
image: ghcr.io/blockscout/sig-provider:${SIG_PROVIDER_DOCKER_TAG:-latest}
pull_policy: always
+ platform: linux/amd64
restart: always
container_name: 'sig-provider'
- ports:
- - 8151:8050
diff --git a/docker-compose/services/docker-compose-smart-contract-verifier.yml b/docker-compose/services/smart-contract-verifier.yml
similarity index 86%
rename from docker-compose/services/docker-compose-smart-contract-verifier.yml
rename to docker-compose/services/smart-contract-verifier.yml
index 2dca6bcb126f..03e633c6958a 100644
--- a/docker-compose/services/docker-compose-smart-contract-verifier.yml
+++ b/docker-compose/services/smart-contract-verifier.yml
@@ -1,12 +1,11 @@
-version: '3.8'
+version: '3.9'
services:
smart-contract-verifier:
image: ghcr.io/blockscout/smart-contract-verifier:${SMART_CONTRACT_VERIFIER_DOCKER_TAG:-latest}
pull_policy: always
+ platform: linux/amd64
restart: always
container_name: 'smart-contract-verifier'
env_file:
- ../envs/common-smart-contract-verifier.env
- ports:
- - 8150:8050
diff --git a/docker-compose/services/stats.yml b/docker-compose/services/stats.yml
new file mode 100644
index 000000000000..85bee46b9909
--- /dev/null
+++ b/docker-compose/services/stats.yml
@@ -0,0 +1,55 @@
+version: '3.9'
+
+services:
+ stats-db-init:
+ image: postgres:14
+ volumes:
+ - ./stats-db-data:/var/lib/postgresql/data
+ entrypoint:
+ - sh
+ - -c
+ - |
+ chown -R 2000:2000 /var/lib/postgresql/data
+
+ stats-db:
+ depends_on:
+ stats-db-init:
+ condition: service_completed_successfully
+ image: postgres:14
+ user: 2000:2000
+ shm_size: 256m
+ restart: always
+ container_name: 'stats-postgres'
+ command: postgres -c 'max_connections=200'
+ environment:
+ POSTGRES_DB: 'stats'
+ POSTGRES_USER: 'stats'
+ POSTGRES_PASSWORD: 'n0uejXPl61ci6ldCuE2gQU5Y'
+ ports:
+ - 7433:5432
+ volumes:
+ - ./stats-db-data:/var/lib/postgresql/data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U stats -d stats"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ start_period: 10s
+
+ stats:
+ image: ghcr.io/blockscout/stats:${STATS_DOCKER_TAG:-latest}
+ pull_policy: always
+ platform: linux/amd64
+ restart: always
+ container_name: 'stats'
+ depends_on:
+ - "stats-db"
+ extra_hosts:
+ - 'host.docker.internal:host-gateway'
+ env_file:
+ - ../envs/common-stats.env
+ environment:
+ - STATS__DB_URL=postgres://stats:n0uejXPl61ci6ldCuE2gQU5Y@stats-db:5432/stats
+ - STATS__BLOCKSCOUT_DB_URL=${STATS__BLOCKSCOUT_DB_URL-postgresql://blockscout:ceWb1MeLBEeOIfk65gU8EjF8@db:5432/blockscout}
+ - STATS__CREATE_DATABASE=true
+ - STATS__RUN_MIGRATIONS=true
diff --git a/docker-compose/services/docker-compose-visualizer.yml b/docker-compose/services/visualizer.yml
similarity index 83%
rename from docker-compose/services/docker-compose-visualizer.yml
rename to docker-compose/services/visualizer.yml
index a13030e79b74..37a4aaad880b 100644
--- a/docker-compose/services/docker-compose-visualizer.yml
+++ b/docker-compose/services/visualizer.yml
@@ -1,12 +1,11 @@
-version: '3.8'
+version: '3.9'
services:
visualizer:
image: ghcr.io/blockscout/visualizer:${VISUALIZER_DOCKER_TAG:-latest}
pull_policy: always
+ platform: linux/amd64
restart: always
container_name: 'visualizer'
env_file:
- ../envs/common-visualizer.env
- ports:
- - 8152:8050
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 4b8018c39be2..bc8bb581b4b8 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1,22 +1,13 @@
-FROM bitwalker/alpine-elixir-phoenix:1.14 AS builder
+FROM hexpm/elixir:1.14.5-erlang-24.2.2-alpine-3.18.2 AS builder
WORKDIR /app
-RUN apk --no-cache --update add alpine-sdk gmp-dev automake libtool inotify-tools autoconf python3 file qemu-x86_64
+ENV MIX_ENV="prod"
-ENV GLIBC_REPO=https://github.com/sgerrand/alpine-pkg-glibc \
- GLIBC_VERSION=2.30-r0 \
- PORT=4000 \
- MIX_ENV="prod" \
- SECRET_KEY_BASE="RMgI4C1HSkxsEjdhtGMfwAHfyT6CKWXOgzCboJflfSm4jeAlic52io05KB6mqzc5"
+RUN apk --no-cache --update add alpine-sdk gmp-dev automake libtool inotify-tools autoconf python3 file gcompat
RUN set -ex && \
- apk --update add libstdc++ curl ca-certificates && \
- for pkg in glibc-${GLIBC_VERSION} glibc-bin-${GLIBC_VERSION}; \
- do curl -sSL ${GLIBC_REPO}/releases/download/${GLIBC_VERSION}/${pkg}.apk -o /tmp/${pkg}.apk; done && \
- apk add --allow-untrusted /tmp/*.apk && \
- rm -v /tmp/*.apk && \
- /usr/glibc-compat/sbin/ldconfig /lib /usr/glibc-compat/lib
+ apk --update add libstdc++ curl ca-certificates gcompat
ARG CACHE_EXCHANGE_RATES_PERIOD
ARG API_V1_READ_METHODS_DISABLED
@@ -30,6 +21,8 @@ ARG MIXPANEL_TOKEN
ARG MIXPANEL_URL
ARG AMPLITUDE_API_KEY
ARG AMPLITUDE_URL
+ARG CHAIN_TYPE
+ENV CHAIN_TYPE=${CHAIN_TYPE}
# Cache elixir deps
ADD mix.exs mix.lock ./
@@ -38,11 +31,16 @@ ADD apps/explorer/mix.exs ./apps/explorer/
ADD apps/ethereum_jsonrpc/mix.exs ./apps/ethereum_jsonrpc/
ADD apps/indexer/mix.exs ./apps/indexer/
+ENV MIX_HOME=/opt/mix
+RUN mix local.hex --force
RUN mix do deps.get, local.rebar --force, deps.compile
-ADD . .
+ADD apps ./apps
+ADD config ./config
+ADD rel ./rel
+ADD *.exs ./
-COPY . .
+RUN apk add --update nodejs npm
# Run forderground build and phoenix digest
RUN mix compile && npm install npm@latest
@@ -56,6 +54,9 @@ RUN cd apps/block_scout_web/assets/ && \
apk update && \
apk del --force-broken-world alpine-sdk gmp-dev automake libtool inotify-tools autoconf python3
+
+RUN apk add --update git make
+
RUN mix phx.digest
RUN mkdir -p /opt/release \
@@ -63,14 +64,16 @@ RUN mkdir -p /opt/release \
&& mv _build/${MIX_ENV}/rel/blockscout /opt/release
##############################################################
-FROM bitwalker/alpine-elixir-phoenix:1.14
+FROM hexpm/elixir:1.14.5-erlang-24.2.2-alpine-3.18.2
ARG RELEASE_VERSION
ENV RELEASE_VERSION=${RELEASE_VERSION}
+ARG CHAIN_TYPE
+ENV CHAIN_TYPE=${CHAIN_TYPE}
ARG BLOCKSCOUT_VERSION
ENV BLOCKSCOUT_VERSION=${BLOCKSCOUT_VERSION}
-RUN apk --no-cache --update add jq
+RUN apk --no-cache --update add jq curl
WORKDIR /app
@@ -78,4 +81,3 @@ COPY --from=builder /opt/release/blockscout .
COPY --from=builder /app/apps/explorer/node_modules ./node_modules
COPY --from=builder /app/config/config_helper.exs ./config/config_helper.exs
COPY --from=builder /app/config/config_helper.exs /app/releases/${RELEASE_VERSION}/config_helper.exs
-
diff --git a/docker/Makefile b/docker/Makefile
index fff407f21214..63bcd32de63b 100644
--- a/docker/Makefile
+++ b/docker/Makefile
@@ -1,782 +1,91 @@
-SYSTEM := $(shell uname -s)
-HOST := host.docker.internal
DOCKER_REPO := blockscout
-APP_NAME := blockscout
-BS_CONTAINER_IMAGE := $(DOCKER_REPO)/$(APP_NAME)
-BS_CONTAINER_NAME := blockscout
-PG_CONTAINER_IMAGE := postgres:14
-PG_CONTAINER_NAME := db
-THIS_FILE = $(lastword $(MAKEFILE_LIST))
-RELEASE_VERSION ?= '5.1.5'
-PORT ?= '4000'
+BACKEND_APP_NAME := blockscout
+FRONTEND_APP_NAME := frontend
+BACKEND_CONTAINER_IMAGE := $(DOCKER_REPO)/$(BACKEND_APP_NAME)
+BACKEND_CONTAINER_NAME := backend
+FRONTEND_CONTAINER_NAME := frontend
+VISUALIZER_CONTAINER_NAME := visualizer
+SIG_PROVIDER_CONTAINER_NAME := sig-provider
+STATS_CONTAINER_NAME := stats
+STATS_DB_CONTAINER_NAME := stats-postgres
+PROXY_CONTAINER_NAME := proxy
+PG_CONTAINER_NAME := postgres
+RELEASE_VERSION ?= '6.0.0'
TAG := $(RELEASE_VERSION)-commit-$(shell git log -1 --pretty=format:"%h")
STABLE_TAG := $(RELEASE_VERSION)
-ifeq ($(SYSTEM), Linux)
- HOST=localhost
-endif
-
-ifdef DATABASE_URL
- DB_URL = $(DATABASE_URL)
+start:
+ @echo "==> Starting blockscout db"
+ @docker-compose -f ../docker-compose/services/db.yml up -d
+ @echo "==> Starting blockscout backend"
+ @docker-compose -f ../docker-compose/services/backend.yml up -d
+ @echo "==> Starting stats microservice"
+ @docker-compose -f ../docker-compose/services/stats.yml up -d
+ @echo "==> Starting visualizer microservice"
+ @docker-compose -f ../docker-compose/services/visualizer.yml up -d
+ @echo "==> Starting sig-provider microservice"
+ @docker-compose -f ../docker-compose/services/sig-provider.yml up -d
+ @echo "==> Starting blockscout frontend"
+ @docker-compose -f ../docker-compose/services/frontend.yml up -d
+ @echo "==> Starting Nginx proxy"
+ @docker-compose -f ../docker-compose/services/nginx.yml up -d
+
+BS_BACKEND_STARTED := $(shell docker ps --no-trunc --filter name=^/${BACKEND_CONTAINER_NAME}$ | grep ${BACKEND_CONTAINER_NAME})
+BS_FRONTEND_STARTED := $(shell docker ps --no-trunc --filter name=^/${FRONTEND_CONTAINER_NAME}$ | grep ${FRONTEND_CONTAINER_NAME})
+BS_STATS_STARTED := $(shell docker ps --no-trunc --filter name=^/${STATS_CONTAINER_NAME}$ | grep ${STATS_CONTAINER_NAME})
+BS_STATS_DB_STARTED := $(shell docker ps --no-trunc --filter name=^/${STATS_DB_CONTAINER_NAME}$ | grep ${STATS_DB_CONTAINER_NAME})
+BS_VISUALIZER_STARTED := $(shell docker ps --no-trunc --filter name=^/${VISUALIZER_CONTAINER_NAME}$ | grep ${VISUALIZER_CONTAINER_NAME})
+BS_SIG_PROVIDER_STARTED := $(shell docker ps --no-trunc --filter name=^/${SIG_PROVIDER_CONTAINER_NAME}$ | grep ${SIG_PROVIDER_CONTAINER_NAME})
+BS_PROXY_STARTED := $(shell docker ps --no-trunc --filter name=^/${PROXY_CONTAINER_NAME}$ | grep ${PROXY_CONTAINER_NAME})
+stop:
+ifdef BS_FRONTEND_STARTED
+ @echo "==> Stopping Blockscout frontend container."
+ @docker stop $(FRONTEND_CONTAINER_NAME) && docker rm -f $(FRONTEND_CONTAINER_NAME)
+ @echo "==> Blockscout frontend container stopped."
else
- DB_URL = postgresql://postgres:@$(HOST):5432/blockscout?ssl=false
- ECTO_USE_SSL = 'false'
-endif
-BLOCKSCOUT_CONTAINER_PARAMS = -e 'MIX_ENV=prod' \
- -e 'DATABASE_URL=$(DB_URL)'
-ifeq ($(SYSTEM), Linux)
- BLOCKSCOUT_CONTAINER_PARAMS += --network=host
-endif
-ifdef NETWORK
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'NETWORK=$(NETWORK)'
-endif
-ifdef SUBNETWORK
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'SUBNETWORK=$(SUBNETWORK)'
-endif
-ifdef LOGO
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'LOGO=$(LOGO)'
-endif
-ifdef ETHEREUM_JSONRPC_VARIANT
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_VARIANT=$(ETHEREUM_JSONRPC_VARIANT)'
-endif
-ifdef ETHEREUM_JSONRPC_HTTP_URL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_HTTP_URL=$(ETHEREUM_JSONRPC_HTTP_URL)'
-endif
-ifdef ETHEREUM_JSONRPC_FALLBACK_HTTP_URL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_FALLBACK_HTTP_URL=$(ETHEREUM_JSONRPC_FALLBACK_HTTP_URL)'
-endif
-ifdef ETHEREUM_JSONRPC_TRACE_URL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_TRACE_URL=$(ETHEREUM_JSONRPC_TRACE_URL)'
-endif
-ifdef ETHEREUM_JSONRPC_FALLBACK_TRACE_URL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_FALLBACK_TRACE_URL=$(ETHEREUM_JSONRPC_FALLBACK_TRACE_URL)'
-endif
-ifdef ETHEREUM_JSONRPC_WS_URL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_WS_URL=$(ETHEREUM_JSONRPC_WS_URL)'
-endif
-ifdef ETHEREUM_JSONRPC_TRANSPORT
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_TRANSPORT=$(ETHEREUM_JSONRPC_TRANSPORT)'
-endif
-ifdef ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES=$(ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES)'
-endif
-ifdef IPC_PATH
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'IPC_PATH=$(IPC_PATH)'
-endif
-ifdef NETWORK_PATH
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'NETWORK_PATH=$(NETWORK_PATH)'
-endif
-ifdef CHECK_ORIGIN
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CHECK_ORIGIN=$(CHECK_ORIGIN)'
-endif
-ifdef COIN
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'COIN=$(COIN)'
-endif
-ifdef METADATA_CONTRACT
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'METADATA_CONTRACT=$(METADATA_CONTRACT)'
-endif
-ifdef VALIDATORS_CONTRACT
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'VALIDATORS_CONTRACT=$(VALIDATORS_CONTRACT)'
-endif
-ifdef KEYS_MANAGER_CONTRACT
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'KEYS_MANAGER_CONTRACT=$(KEYS_MANAGER_CONTRACT)'
-endif
-ifdef SUPPLY_MODULE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'SUPPLY_MODULE=$(SUPPLY_MODULE)'
-endif
-ifdef POOL_SIZE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'POOL_SIZE=$(POOL_SIZE)'
-endif
-ifdef ECTO_USE_SSL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ECTO_USE_SSL=$(ECTO_USE_SSL)'
-endif
-ifdef DATADOG_HOST
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'DATADOG_HOST=$(DATADOG_HOST)'
-endif
-ifdef DATADOG_PORT
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'DATADOG_PORT=$(DATADOG_PORT)'
-endif
-ifdef SPANDEX_BATCH_SIZE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'SPANDEX_BATCH_SIZE=$(SPANDEX_BATCH_SIZE)'
-endif
-ifdef SPANDEX_SYNC_THRESHOLD
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'SPANDEX_SYNC_THRESHOLD=$(SPANDEX_SYNC_THRESHOLD)'
-endif
-ifdef HEART_BEAT_TIMEOUT
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'HEART_BEAT_TIMEOUT=$(HEART_BEAT_TIMEOUT)'
-endif
-ifdef HEART_COMMAND
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'HEART_COMMAND=$(HEART_COMMAND)'
-endif
-ifdef BLOCKSCOUT_VERSION
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'BLOCKSCOUT_VERSION=$(BLOCKSCOUT_VERSION)'
-endif
-ifdef RELEASE_LINK
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'RELEASE_LINK=$(RELEASE_LINK)'
-endif
-ifdef ELIXIR_VERSION
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ELIXIR_VERSION=$(ELIXIR_VERSION)'
-endif
-ifdef BLOCK_TRANSFORMER
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'BLOCK_TRANSFORMER=$(BLOCK_TRANSFORMER)'
-endif
-ifdef GRAPHIQL_TRANSACTION
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'GRAPHIQL_TRANSACTION=$(GRAPHIQL_TRANSACTION)'
-endif
-ifdef BLOCK_RANGES
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'BLOCK_RANGES=$(BLOCK_RANGES)'
-endif
-ifdef FIRST_BLOCK
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'FIRST_BLOCK=$(FIRST_BLOCK)'
-endif
-ifdef LAST_BLOCK
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'LAST_BLOCK=$(LAST_BLOCK)'
-endif
-ifdef TRACE_FIRST_BLOCK
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'TRACE_FIRST_BLOCK=$(TRACE_FIRST_BLOCK)'
-endif
-ifdef TRACE_LAST_BLOCK
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'TRACE_LAST_BLOCK=$(TRACE_LAST_BLOCK)'
-endif
-ifdef CACHE_TXS_COUNT_PERIOD
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CACHE_TXS_COUNT_PERIOD=$(CACHE_TXS_COUNT_PERIOD)'
-endif
-ifdef CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=$(CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL)'
-endif
-ifdef SUPPORTED_CHAINS
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'SUPPORTED_CHAINS=$(SUPPORTED_CHAINS)'
-endif
-ifdef CACHE_BLOCK_COUNT_PERIOD
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CACHE_BLOCK_COUNT_PERIOD=$(CACHE_BLOCK_COUNT_PERIOD)'
-endif
-ifdef CACHE_ADDRESS_SUM_PERIOD
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CACHE_ADDRESS_SUM_PERIOD=$(CACHE_ADDRESS_SUM_PERIOD)'
-endif
-ifdef UNCLES_IN_AVERAGE_BLOCK_TIME
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'UNCLES_IN_AVERAGE_BLOCK_TIME=$(UNCLES_IN_AVERAGE_BLOCK_TIME)'
-endif
-ifdef CACHE_AVERAGE_BLOCK_PERIOD
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CACHE_AVERAGE_BLOCK_PERIOD=$(CACHE_AVERAGE_BLOCK_PERIOD)'
-endif
-ifdef CACHE_MARKET_HISTORY_PERIOD
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CACHE_MARKET_HISTORY_PERIOD=$(CACHE_MARKET_HISTORY_PERIOD)'
-endif
-ifdef DISABLE_WEBAPP
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'DISABLE_WEBAPP=$(DISABLE_WEBAPP)'
-endif
-ifdef API_V1_READ_METHODS_DISABLED
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_V1_READ_METHODS_DISABLED=$(API_V1_READ_METHODS_DISABLED)'
-endif
-ifdef API_V1_WRITE_METHODS_DISABLED
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_V1_WRITE_METHODS_DISABLED=$(API_V1_WRITE_METHODS_DISABLED)'
-endif
-ifdef DISABLE_INDEXER
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'DISABLE_INDEXER=$(DISABLE_INDEXER)'
-endif
-ifdef DISABLE_REALTIME_INDEXER
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'DISABLE_REALTIME_INDEXER=$(DISABLE_REALTIME_INDEXER)'
-endif
-ifdef INDEXER_DISABLE_TOKEN_INSTANCE_REALTIME_FETCHER
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_DISABLE_TOKEN_INSTANCE_REALTIME_FETCHER=$(INDEXER_DISABLE_TOKEN_INSTANCE_REALTIME_FETCHER)'
-endif
-ifdef INDEXER_DISABLE_TOKEN_INSTANCE_RETRY_FETCHER
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_DISABLE_TOKEN_INSTANCE_RETRY_FETCHER=$(INDEXER_DISABLE_TOKEN_INSTANCE_RETRY_FETCHER)'
-endif
-ifdef INDEXER_DISABLE_TOKEN_INSTANCE_SANITIZE_FETCHER
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_DISABLE_TOKEN_INSTANCE_SANITIZE_FETCHER=$(INDEXER_DISABLE_TOKEN_INSTANCE_SANITIZE_FETCHER)'
-endif
-ifdef WEBAPP_URL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'WEBAPP_URL=$(WEBAPP_URL)'
-endif
-ifdef API_URL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_URL=$(API_URL)'
-endif
-ifdef CHAIN_SPEC_PATH
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CHAIN_SPEC_PATH=$(CHAIN_SPEC_PATH)'
-endif
-ifdef EMISSION_FORMAT
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'EMISSION_FORMAT=$(EMISSION_FORMAT)'
-endif
-ifdef REWARDS_CONTRACT
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'REWARDS_CONTRACT=$(REWARDS_CONTRACT)'
-endif
-ifdef SHOW_ADDRESS_MARKETCAP_PERCENTAGE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'SHOW_ADDRESS_MARKETCAP_PERCENTAGE=$(SHOW_ADDRESS_MARKETCAP_PERCENTAGE)'
-endif
-ifdef BLOCKSCOUT_PROTOCOL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'BLOCKSCOUT_PROTOCOL=$(BLOCKSCOUT_PROTOCOL)'
-endif
-ifdef BLOCKSCOUT_HOST
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'BLOCKSCOUT_HOST=$(BLOCKSCOUT_HOST)'
-endif
-ifdef DECOMPILED_SMART_CONTRACT_TOKEN
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'DECOMPILED_SMART_CONTRACT_TOKEN=$(DECOMPILED_SMART_CONTRACT_TOKEN)'
-endif
-ifdef CHECKSUM_ADDRESS_HASHES
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CHECKSUM_ADDRESS_HASHES=$(CHECKSUM_ADDRESS_HASHES)'
-endif
-ifdef CHECKSUM_FUNCTION
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CHECKSUM_FUNCTION=$(CHECKSUM_FUNCTION)'
-endif
-ifdef EXCHANGE_RATES_COIN
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'EXCHANGE_RATES_COIN=$(EXCHANGE_RATES_COIN)'
-endif
-ifdef EXCHANGE_RATES_SOURCE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'EXCHANGE_RATES_SOURCE=$(EXCHANGE_RATES_SOURCE)'
-endif
-ifdef EXCHANGE_RATES_COINGECKO_COIN_ID
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'EXCHANGE_RATES_COINGECKO_COIN_ID=$(EXCHANGE_RATES_COINGECKO_COIN_ID)'
-endif
-ifdef EXCHANGE_RATES_COINGECKO_API_KEY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'EXCHANGE_RATES_COINGECKO_API_KEY=$(EXCHANGE_RATES_COINGECKO_API_KEY)'
-endif
-ifdef EXCHANGE_RATES_COINGECKO_PLATFORM_ID
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'EXCHANGE_RATES_COINGECKO_PLATFORM_ID=$(EXCHANGE_RATES_COINGECKO_PLATFORM_ID)'
-endif
-ifdef EXCHANGE_RATES_COINMARKETCAP_API_KEY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'EXCHANGE_RATES_COINMARKETCAP_API_KEY=$(EXCHANGE_RATES_COINMARKETCAP_API_KEY)'
-endif
-ifdef DISABLE_EXCHANGE_RATES
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'DISABLE_EXCHANGE_RATES=$(DISABLE_EXCHANGE_RATES)'
-endif
-ifdef TOKEN_EXCHANGE_RATE_INTERVAL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'TOKEN_EXCHANGE_RATE_INTERVAL=$(TOKEN_EXCHANGE_RATE_INTERVAL)'
-endif
-ifdef TOKEN_EXCHANGE_RATE_REFETCH_INTERVAL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'TOKEN_EXCHANGE_RATE_REFETCH_INTERVAL=$(TOKEN_EXCHANGE_RATE_REFETCH_INTERVAL)'
-endif
-ifdef TOKEN_EXCHANGE_RATE_MAX_BATCH_SIZE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'TOKEN_EXCHANGE_RATE_MAX_BATCH_SIZE=$(TOKEN_EXCHANGE_RATE_MAX_BATCH_SIZE)'
-endif
-ifdef DISABLE_TOKEN_EXCHANGE_RATE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'DISABLE_TOKEN_EXCHANGE_RATE=$(DISABLE_TOKEN_EXCHANGE_RATE)'
-endif
-ifdef SHOW_PRICE_CHART
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'SHOW_PRICE_CHART=$(SHOW_PRICE_CHART)'
-endif
-ifdef SHOW_PRICE_CHART_LEGEND
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'SHOW_PRICE_CHART_LEGEND=$(SHOW_PRICE_CHART_LEGEND)'
-endif
-ifdef SHOW_TXS_CHART
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'SHOW_TXS_CHART=$(SHOW_TXS_CHART)'
-endif
-ifdef TXS_HISTORIAN_INIT_LAG
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'TXS_HISTORIAN_INIT_LAG=$(TXS_HISTORIAN_INIT_LAG)'
-endif
-ifdef TXS_STATS_DAYS_TO_COMPILE_AT_INIT
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'TXS_STATS_DAYS_TO_COMPILE_AT_INIT=$(TXS_STATS_DAYS_TO_COMPILE_AT_INIT)'
-endif
-ifdef APPS_MENU
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'APPS_MENU=$(APPS_MENU)'
-endif
-ifdef EXTERNAL_APPS
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'EXTERNAL_APPS=$(EXTERNAL_APPS)'
-endif
-ifdef GAS_PRICE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'GAS_PRICE=$(GAS_PRICE)'
-endif
-ifdef GAS_PRICE_ORACLE_NUM_OF_BLOCKS
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'GAS_PRICE_ORACLE_NUM_OF_BLOCKS=$(GAS_PRICE_ORACLE_NUM_OF_BLOCKS)'
-endif
-ifdef GAS_PRICE_ORACLE_SAFELOW_PERCENTILE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'GAS_PRICE_ORACLE_SAFELOW_PERCENTILE=$(GAS_PRICE_ORACLE_SAFELOW_PERCENTILE)'
-endif
-ifdef GAS_PRICE_ORACLE_AVERAGE_PERCENTILE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'GAS_PRICE_ORACLE_AVERAGE_PERCENTILE=$(GAS_PRICE_ORACLE_AVERAGE_PERCENTILE)'
-endif
-ifdef GAS_PRICE_ORACLE_FAST_PERCENTILE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'GAS_PRICE_ORACLE_FAST_PERCENTILE=$(GAS_PRICE_ORACLE_FAST_PERCENTILE)'
-endif
-ifdef GAS_PRICE_ORACLE_CACHE_PERIOD
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'GAS_PRICE_ORACLE_CACHE_PERIOD=$(GAS_PRICE_ORACLE_CACHE_PERIOD)'
-endif
-ifdef TOKEN_METADATA_UPDATE_INTERVAL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'TOKEN_METADATA_UPDATE_INTERVAL=$(TOKEN_METADATA_UPDATE_INTERVAL)'
-endif
-ifdef RESTRICTED_LIST
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'RESTRICTED_LIST=$(RESTRICTED_LIST)'
-endif
-ifdef RESTRICTED_LIST_KEY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'RESTRICTED_LIST_KEY=$(RESTRICTED_LIST_KEY)'
-endif
-ifdef CACHE_ADDRESS_TRANSACTIONS_COUNTER_PERIOD
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CACHE_ADDRESS_TRANSACTIONS_COUNTER_PERIOD=$(CACHE_ADDRESS_TRANSACTIONS_COUNTER_PERIOD)'
-endif
-ifdef CACHE_ADDRESS_TRANSACTIONS_GAS_USAGE_COUNTER_PERIOD
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CACHE_ADDRESS_TRANSACTIONS_GAS_USAGE_COUNTER_PERIOD=$(CACHE_ADDRESS_TRANSACTIONS_GAS_USAGE_COUNTER_PERIOD)'
-endif
-ifdef CACHE_TOTAL_GAS_USAGE_PERIOD
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CACHE_TOTAL_GAS_USAGE_PERIOD=$(CACHE_TOTAL_GAS_USAGE_PERIOD)'
-endif
-ifdef CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED=$(CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED)'
-endif
-ifdef DISABLE_LP_TOKENS_IN_MARKET_CAP
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'DISABLE_LP_TOKENS_IN_MARKET_CAP=$(DISABLE_LP_TOKENS_IN_MARKET_CAP)'
-endif
-ifdef HIDE_BLOCK_MINER
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'HIDE_BLOCK_MINER=$(HIDE_BLOCK_MINER)'
-endif
-ifdef COIN_BALANCE_HISTORY_DAYS
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'COIN_BALANCE_HISTORY_DAYS=$(COIN_BALANCE_HISTORY_DAYS)'
-endif
-ifdef CACHE_ADDRESS_TOKENS_USD_SUM_PERIOD
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CACHE_ADDRESS_TOKENS_USD_SUM_PERIOD=$(CACHE_ADDRESS_TOKENS_USD_SUM_PERIOD)'
-endif
-ifdef SHOW_MAINTENANCE_ALERT
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'SHOW_MAINTENANCE_ALERT=$(SHOW_MAINTENANCE_ALERT)'
-endif
-ifdef MAINTENANCE_ALERT_MESSAGE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'MAINTENANCE_ALERT_MESSAGE=$(MAINTENANCE_ALERT_MESSAGE)'
-endif
-ifdef SOURCIFY_INTEGRATION_ENABLED
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'SOURCIFY_INTEGRATION_ENABLED=$(SOURCIFY_INTEGRATION_ENABLED)'
-endif
-ifdef SOURCIFY_SERVER_URL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'SOURCIFY_SERVER_URL=$(SOURCIFY_SERVER_URL)'
-endif
-ifdef SOURCIFY_REPO_URL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'SOURCIFY_REPO_URL=$(SOURCIFY_REPO_URL)'
-endif
-ifdef CHAIN_ID
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CHAIN_ID=$(CHAIN_ID)'
-endif
-ifdef JSON_RPC
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'JSON_RPC=$(JSON_RPC)'
-endif
-ifdef MAX_SIZE_UNLESS_HIDE_ARRAY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'MAX_SIZE_UNLESS_HIDE_ARRAY=$(MAX_SIZE_UNLESS_HIDE_ARRAY)'
-endif
-ifdef DISPLAY_TOKEN_ICONS
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'DISPLAY_TOKEN_ICONS=$(DISPLAY_TOKEN_ICONS)'
-endif
-ifdef SHOW_TENDERLY_LINK
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'SHOW_TENDERLY_LINK=$(SHOW_TENDERLY_LINK)'
-endif
-ifdef TENDERLY_CHAIN_PATH
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'TENDERLY_CHAIN_PATH=$(TENDERLY_CHAIN_PATH)'
-endif
-ifdef RE_CAPTCHA_SECRET_KEY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'RE_CAPTCHA_SECRET_KEY=$(RE_CAPTCHA_SECRET_KEY)'
-endif
-ifdef RE_CAPTCHA_CLIENT_KEY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'RE_CAPTCHA_CLIENT_KEY=$(RE_CAPTCHA_CLIENT_KEY)'
-endif
-ifdef RE_CAPTCHA_V3_SECRET_KEY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'RE_CAPTCHA_V3_SECRET_KEY=$(RE_CAPTCHA_V3_SECRET_KEY)'
-endif
-ifdef RE_CAPTCHA_V3_CLIENT_KEY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'RE_CAPTCHA_V3_CLIENT_KEY=$(RE_CAPTCHA_V3_CLIENT_KEY)'
-endif
-ifdef RE_CAPTCHA_DISABLED
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'RE_CAPTCHA_DISABLED=$(RE_CAPTCHA_DISABLED)'
-endif
-ifdef CACHE_ADDRESS_TOKEN_TRANSFERS_COUNTER_PERIOD
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CACHE_ADDRESS_TOKEN_TRANSFERS_COUNTER_PERIOD=$(CACHE_ADDRESS_TOKEN_TRANSFERS_COUNTER_PERIOD)'
-endif
-ifdef API_RATE_LIMIT
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_RATE_LIMIT=$(API_RATE_LIMIT)'
-endif
-ifdef API_RATE_LIMIT_BY_KEY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_RATE_LIMIT_BY_KEY=$(API_RATE_LIMIT_BY_KEY)'
+ @echo "==> Blockscout frontend container already stopped before."
endif
-ifdef API_RATE_LIMIT_BY_WHITELISTED_IP
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_RATE_LIMIT_BY_WHITELISTED_IP=$(API_RATE_LIMIT_BY_WHITELISTED_IP)'
-endif
-ifdef API_RATE_LIMIT_STATIC_API_KEY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_RATE_LIMIT_STATIC_API_KEY=$(API_RATE_LIMIT_STATIC_API_KEY)'
-endif
-ifdef API_RATE_LIMIT_WHITELISTED_IPS
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_RATE_LIMIT_WHITELISTED_IPS=$(API_RATE_LIMIT_WHITELISTED_IPS)'
-endif
-ifdef API_RATE_LIMIT_DISABLED
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_RATE_LIMIT_DISABLED=$(API_RATE_LIMIT_DISABLED)'
-endif
-ifdef API_RATE_LIMIT_HAMMER_REDIS_URL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_RATE_LIMIT_HAMMER_REDIS_URL=$(API_RATE_LIMIT_HAMMER_REDIS_URL)'
-endif
-ifdef API_RATE_LIMIT_IS_BLOCKSCOUT_BEHIND_PROXY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_RATE_LIMIT_IS_BLOCKSCOUT_BEHIND_PROXY=$(API_RATE_LIMIT_IS_BLOCKSCOUT_BEHIND_PROXY)'
-endif
-ifdef API_RATE_LIMIT_UI_V2_WITH_TOKEN
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_RATE_LIMIT_UI_V2_WITH_TOKEN=$(API_RATE_LIMIT_UI_V2_WITH_TOKEN)'
-endif
-ifdef API_RATE_LIMIT_BY_IP
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_RATE_LIMIT_BY_IP=$(API_RATE_LIMIT_BY_IP)'
-endif
-ifdef API_RATE_LIMIT_TIME_INTERVAL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_RATE_LIMIT_TIME_INTERVAL=$(API_RATE_LIMIT_TIME_INTERVAL)'
-endif
-ifdef API_RATE_LIMIT_BY_IP_TIME_INTERVAL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_RATE_LIMIT_BY_IP_TIME_INTERVAL=$(API_RATE_LIMIT_BY_IP_TIME_INTERVAL)'
-endif
-ifdef API_RATE_LIMIT_UI_V2_TOKEN_TTL_IN_SECONDS
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_RATE_LIMIT_UI_V2_TOKEN_TTL_IN_SECONDS=$(API_RATE_LIMIT_UI_V2_TOKEN_TTL_IN_SECONDS)'
-endif
-ifdef TOKEN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'TOKEN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD=$(TOKEN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD)'
-endif
-ifdef COIN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'COIN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD=$(COIN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD)'
-endif
-ifdef ETHEREUM_JSONRPC_DEBUG_TRACE_TRANSACTION_TIMEOUT
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_DEBUG_TRACE_TRANSACTION_TIMEOUT=$(ETHEREUM_JSONRPC_DEBUG_TRACE_TRANSACTION_TIMEOUT)'
-endif
-ifdef ETHEREUM_JSONRPC_HTTP_TIMEOUT
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_HTTP_TIMEOUT=$(ETHEREUM_JSONRPC_HTTP_TIMEOUT)'
-endif
-ifdef FETCH_REWARDS_WAY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'FETCH_REWARDS_WAY=$(FETCH_REWARDS_WAY)'
-endif
-ifdef FOOTER_LOGO
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'FOOTER_LOGO=$(FOOTER_LOGO)'
-endif
-ifdef FOOTER_CHAT_LINK
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'FOOTER_CHAT_LINK=$(FOOTER_CHAT_LINK)'
-endif
-ifdef FOOTER_GITHUB_LINK
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'FOOTER_GITHUB_LINK=$(FOOTER_GITHUB_LINK)'
-endif
-ifdef FOOTER_FORUM_LINK_ENABLED
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'FOOTER_FORUM_LINK_ENABLED=$(FOOTER_FORUM_LINK_ENABLED)'
-endif
-ifdef FOOTER_FORUM_LINK
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'FOOTER_FORUM_LINK=$(FOOTER_FORUM_LINK)'
-endif
-ifdef FOOTER_TELEGRAM_LINK_ENABLED
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'FOOTER_TELEGRAM_LINK_ENABLED=$(FOOTER_TELEGRAM_LINK_ENABLED)'
-endif
-ifdef FOOTER_TELEGRAM_LINK
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'FOOTER_TELEGRAM_LINK=$(FOOTER_TELEGRAM_LINK)'
-endif
-ifdef FOOTER_LINK_TO_OTHER_EXPLORERS
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'FOOTER_LINK_TO_OTHER_EXPLORERS=$(FOOTER_LINK_TO_OTHER_EXPLORERS)'
-endif
-ifdef FOOTER_OTHER_EXPLORERS
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'FOOTER_OTHER_EXPLORERS=$(FOOTER_OTHER_EXPLORERS)'
-endif
-ifdef NETWORK_ICON
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'NETWORK_ICON=$(NETWORK_ICON)'
-endif
-ifdef LOGO_TEXT
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'LOGO_TEXT=$(LOGO_TEXT)'
-endif
-ifdef SHOW_TESTNET_LABEL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'SHOW_TESTNET_LABEL=$(SHOW_TESTNET_LABEL)'
-endif
-ifdef TESTNET_LABEL_TEXT
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'TESTNET_LABEL_TEXT=$(TESTNET_LABEL_TEXT)'
-endif
-ifdef CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST=$(CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST)'
-endif
-ifdef CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST_V_0_5
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST_V_0_5=$(CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST_V_0_5)'
-endif
-ifdef CUSTOM_CONTRACT_ADDRESSES_CIRCLES
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CUSTOM_CONTRACT_ADDRESSES_CIRCLES=$(CUSTOM_CONTRACT_ADDRESSES_CIRCLES)'
-endif
-ifdef HEALTHY_BLOCKS_PERIOD
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'HEALTHY_BLOCKS_PERIOD=$(HEALTHY_BLOCKS_PERIOD)'
-endif
-ifdef EXCHANGE_RATES_FETCH_BTC_VALUE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'EXCHANGE_RATES_FETCH_BTC_VALUE=$(EXCHANGE_RATES_FETCH_BTC_VALUE)'
-endif
-ifdef TXS_STATS_ENABLED
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'TXS_STATS_ENABLED=$(TXS_STATS_ENABLED)'
-endif
-ifdef INDEXER_MEMORY_LIMIT
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_MEMORY_LIMIT=$(INDEXER_MEMORY_LIMIT)'
-endif
-ifdef INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER=$(INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER)'
-endif
-ifdef INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=$(INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER)'
-endif
-ifdef INDEXER_DISABLE_BLOCK_REWARD_FETCHER
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_DISABLE_BLOCK_REWARD_FETCHER=$(INDEXER_DISABLE_BLOCK_REWARD_FETCHER)'
-endif
-ifdef INDEXER_DISABLE_ADDRESS_COIN_BALANCE_FETCHER
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_DISABLE_ADDRESS_COIN_BALANCE_FETCHER=$(INDEXER_DISABLE_ADDRESS_COIN_BALANCE_FETCHER)'
-endif
-ifdef INDEXER_DISABLE_CATALOGED_TOKEN_UPDATER_FETCHER
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_DISABLE_CATALOGED_TOKEN_UPDATER_FETCHER=$(INDEXER_DISABLE_CATALOGED_TOKEN_UPDATER_FETCHER)'
-endif
-ifdef INDEXER_DISABLE_EMPTY_BLOCKS_SANITIZER
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_DISABLE_EMPTY_BLOCKS_SANITIZER=$(INDEXER_DISABLE_EMPTY_BLOCKS_SANITIZER)'
-endif
-ifdef INDEXER_CATCHUP_BLOCKS_BATCH_SIZE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_CATCHUP_BLOCKS_BATCH_SIZE=$(INDEXER_CATCHUP_BLOCKS_BATCH_SIZE)'
-endif
-ifdef INDEXER_CATCHUP_BLOCKS_CONCURRENCY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_CATCHUP_BLOCKS_CONCURRENCY=$(INDEXER_CATCHUP_BLOCKS_CONCURRENCY)'
-endif
-ifdef INDEXER_CATCHUP_BLOCK_INTERVAL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_CATCHUP_BLOCK_INTERVAL=$(INDEXER_CATCHUP_BLOCK_INTERVAL)'
-endif
-ifdef INDEXER_INTERNAL_TRANSACTIONS_BATCH_SIZE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_INTERNAL_TRANSACTIONS_BATCH_SIZE=$(INDEXER_INTERNAL_TRANSACTIONS_BATCH_SIZE)'
-endif
-ifdef INDEXER_INTERNAL_TRANSACTIONS_CONCURRENCY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_INTERNAL_TRANSACTIONS_CONCURRENCY=$(INDEXER_INTERNAL_TRANSACTIONS_CONCURRENCY)'
-endif
-ifdef INDEXER_BLOCK_REWARD_BATCH_SIZE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_BLOCK_REWARD_BATCH_SIZE=$(INDEXER_BLOCK_REWARD_BATCH_SIZE)'
-endif
-ifdef INDEXER_BLOCK_REWARD_CONCURRENCY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_BLOCK_REWARD_CONCURRENCY=$(INDEXER_BLOCK_REWARD_CONCURRENCY)'
-endif
-ifdef INDEXER_TOKEN_INSTANCE_RETRY_REFETCH_INTERVAL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_TOKEN_INSTANCE_RETRY_REFETCH_INTERVAL=$(INDEXER_TOKEN_INSTANCE_RETRY_REFETCH_INTERVAL)'
-endif
-ifdef INDEXER_TOKEN_INSTANCE_RETRY_CONCURRENCY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_TOKEN_INSTANCE_RETRY_CONCURRENCY=$(INDEXER_TOKEN_INSTANCE_RETRY_CONCURRENCY)'
-endif
-ifdef INDEXER_TOKEN_INSTANCE_REALTIME_CONCURRENCY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_TOKEN_INSTANCE_REALTIME_CONCURRENCY=$(INDEXER_TOKEN_INSTANCE_REALTIME_CONCURRENCY)'
-endif
-ifdef INDEXER_TOKEN_INSTANCE_SANITIZE_CONCURRENCY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_TOKEN_INSTANCE_SANITIZE_CONCURRENCY=$(INDEXER_TOKEN_INSTANCE_SANITIZE_CONCURRENCY)'
-endif
-ifdef INDEXER_COIN_BALANCES_BATCH_SIZE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_RECEIPTS_BATCH_SIZE=$(INDEXER_RECEIPTS_BATCH_SIZE)'
-endif
-ifdef INDEXER_COIN_BALANCES_CONCURRENCY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_RECEIPTS_CONCURRENCY=$(INDEXER_RECEIPTS_CONCURRENCY)'
-endif
-ifdef INDEXER_RECEIPTS_BATCH_SIZE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_RECEIPTS_BATCH_SIZE=$(INDEXER_RECEIPTS_BATCH_SIZE)'
-endif
-ifdef INDEXER_RECEIPTS_CONCURRENCY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_RECEIPTS_CONCURRENCY=$(INDEXER_RECEIPTS_CONCURRENCY)'
-endif
-ifdef INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE=$(INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE)'
-endif
-ifdef INDEXER_TOKEN_BALANCES_BATCH_SIZE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_TOKEN_BALANCES_BATCH_SIZE=$(INDEXER_TOKEN_BALANCES_BATCH_SIZE)'
-endif
-ifdef INDEXER_REALTIME_FETCHER_MAX_GAP
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_REALTIME_FETCHER_MAX_GAP=$(INDEXER_REALTIME_FETCHER_MAX_GAP)'
-endif
-ifdef INDEXER_INTERNAL_TRANSACTIONS_TRACER_TYPE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_INTERNAL_TRANSACTIONS_TRACER_TYPE=$(INDEXER_INTERNAL_TRANSACTIONS_TRACER_TYPE)'
-endif
-ifdef INDEXER_DISABLE_WITHDRAWALS_FETCHER
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_DISABLE_WITHDRAWALS_FETCHER=$(INDEXER_DISABLE_WITHDRAWALS_FETCHER)'
-endif
-ifdef WITHDRAWALS_FIRST_BLOCK
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'WITHDRAWALS_FIRST_BLOCK=$(WITHDRAWALS_FIRST_BLOCK)'
-endif
-ifdef TOKEN_ID_MIGRATION_FIRST_BLOCK
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'TOKEN_ID_MIGRATION_FIRST_BLOCK=$(TOKEN_ID_MIGRATION_FIRST_BLOCK)'
-endif
-ifdef TOKEN_ID_MIGRATION_CONCURRENCY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'TOKEN_ID_MIGRATION_CONCURRENCY=$(TOKEN_ID_MIGRATION_CONCURRENCY)'
-endif
-ifdef TOKEN_ID_MIGRATION_BATCH_SIZE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'TOKEN_ID_MIGRATION_BATCH_SIZE=$(TOKEN_ID_MIGRATION_BATCH_SIZE)'
-endif
-ifdef INDEXER_TX_ACTIONS_ENABLE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_TX_ACTIONS_ENABLE=$(INDEXER_TX_ACTIONS_ENABLE)'
-endif
-ifdef INDEXER_TX_ACTIONS_MAX_TOKEN_CACHE_SIZE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_TX_ACTIONS_MAX_TOKEN_CACHE_SIZE=$(INDEXER_TX_ACTIONS_MAX_TOKEN_CACHE_SIZE)'
-endif
-ifdef INDEXER_TX_ACTIONS_REINDEX_FIRST_BLOCK
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_TX_ACTIONS_REINDEX_FIRST_BLOCK=$(INDEXER_TX_ACTIONS_REINDEX_FIRST_BLOCK)'
-endif
-ifdef INDEXER_TX_ACTIONS_REINDEX_LAST_BLOCK
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_TX_ACTIONS_REINDEX_LAST_BLOCK=$(INDEXER_TX_ACTIONS_REINDEX_LAST_BLOCK)'
-endif
-ifdef INDEXER_TX_ACTIONS_REINDEX_PROTOCOLS
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_TX_ACTIONS_REINDEX_PROTOCOLS=$(INDEXER_TX_ACTIONS_REINDEX_PROTOCOLS)'
-endif
-ifdef INDEXER_TX_ACTIONS_AAVE_V3_POOL_CONTRACT
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_TX_ACTIONS_AAVE_V3_POOL_CONTRACT=$(INDEXER_TX_ACTIONS_AAVE_V3_POOL_CONTRACT)'
-endif
-ifdef SECRET_KEY_BASE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'SECRET_KEY_BASE=$(SECRET_KEY_BASE)'
-endif
-ifdef PORT
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'PORT=$(PORT)'
-endif
-ifdef DATABASE_READ_ONLY_API_URL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'DATABASE_READ_ONLY_API_URL=$(DATABASE_READ_ONLY_API_URL)'
-endif
-ifdef POOL_SIZE_API
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'POOL_SIZE_API=$(POOL_SIZE_API)'
-endif
-ifdef MICROSERVICE_SC_VERIFIER_ENABLED
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'MICROSERVICE_SC_VERIFIER_ENABLED=$(MICROSERVICE_SC_VERIFIER_ENABLED)'
-endif
-ifdef MICROSERVICE_SC_VERIFIER_URL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'MICROSERVICE_SC_VERIFIER_URL=$(MICROSERVICE_SC_VERIFIER_URL)'
-endif
-ifdef MICROSERVICE_SC_VERIFIER_TYPE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'MICROSERVICE_SC_VERIFIER_TYPE=$(MICROSERVICE_SC_VERIFIER_TYPE)'
-endif
-ifdef MICROSERVICE_ETH_BYTECODE_DB_INTERVAL_BETWEEN_LOOKUPS
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'MICROSERVICE_ETH_BYTECODE_DB_INTERVAL_BETWEEN_LOOKUPS=$(MICROSERVICE_ETH_BYTECODE_DB_INTERVAL_BETWEEN_LOOKUPS)'
-endif
-ifdef MICROSERVICE_VISUALIZE_SOL2UML_ENABLED
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'MICROSERVICE_VISUALIZE_SOL2UML_ENABLED=$(MICROSERVICE_VISUALIZE_SOL2UML_ENABLED)'
-endif
-ifdef MICROSERVICE_VISUALIZE_SOL2UML_URL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'MICROSERVICE_VISUALIZE_SOL2UML_URL=$(MICROSERVICE_VISUALIZE_SOL2UML_URL)'
-endif
-ifdef MICROSERVICE_SIG_PROVIDER_ENABLED
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'MICROSERVICE_SIG_PROVIDER_ENABLED=$(MICROSERVICE_SIG_PROVIDER_ENABLED)'
-endif
-ifdef MICROSERVICE_SIG_PROVIDER_URL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'MICROSERVICE_SIG_PROVIDER_URL=$(MICROSERVICE_SIG_PROVIDER_URL)'
-endif
-ifdef ACCOUNT_ENABLED
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_ENABLED=$(ACCOUNT_ENABLED)'
-endif
-ifdef ACCOUNT_REDIS_URL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_REDIS_URL=$(ACCOUNT_REDIS_URL)'
-endif
-ifdef COIN_NAME
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'COIN_NAME=$(COIN_NAME)'
-endif
-ifdef ACCOUNT_AUTH0_DOMAIN
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_AUTH0_DOMAIN=$(ACCOUNT_AUTH0_DOMAIN)'
-endif
-ifdef ACCOUNT_AUTH0_CLIENT_ID
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_AUTH0_CLIENT_ID=$(ACCOUNT_AUTH0_CLIENT_ID)'
-endif
-ifdef ACCOUNT_AUTH0_CLIENT_SECRET
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_AUTH0_CLIENT_SECRET=$(ACCOUNT_AUTH0_CLIENT_SECRET)'
-endif
-ifdef ACCOUNT_SENDGRID_API_KEY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_SENDGRID_API_KEY=$(ACCOUNT_SENDGRID_API_KEY)'
-endif
-ifdef ACCOUNT_SENDGRID_SENDER
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_SENDGRID_SENDER=$(ACCOUNT_SENDGRID_SENDER)'
-endif
-ifdef ACCOUNT_SENDGRID_TEMPLATE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_SENDGRID_TEMPLATE=$(ACCOUNT_SENDGRID_TEMPLATE)'
-endif
-ifdef ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL=$(ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL)'
-endif
-ifdef ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY=$(ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY)'
-endif
-ifdef ACCOUNT_DATABASE_URL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_DATABASE_URL=$(ACCOUNT_DATABASE_URL)'
-endif
-ifdef ACCOUNT_POOL_SIZE
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_POOL_SIZE=$(ACCOUNT_POOL_SIZE)'
-endif
-ifdef ACCOUNT_CLOAK_KEY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_CLOAK_KEY=$(ACCOUNT_CLOAK_KEY)'
-endif
-ifdef MIXPANEL_TOKEN
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'MIXPANEL_TOKEN=$(MIXPANEL_TOKEN)'
-endif
-ifdef MIXPANEL_URL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'MIXPANEL_URL=$(MIXPANEL_URL)'
-endif
-ifdef AMPLITUDE_API_KEY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'AMPLITUDE_API_KEY=$(AMPLITUDE_API_KEY)'
-endif
-ifdef AMPLITUDE_URL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'AMPLITUDE_URL=$(AMPLITUDE_URL)'
-endif
-ifdef DECODE_NOT_A_CONTRACT_CALLS
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'DECODE_NOT_A_CONTRACT_CALLS=$(DECODE_NOT_A_CONTRACT_CALLS)'
-endif
-ifdef CONTRACT_VERIFICATION_ALLOWED_EVM_VERSIONS
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CONTRACT_VERIFICATION_ALLOWED_EVM_VERSIONS=$(CONTRACT_VERIFICATION_ALLOWED_EVM_VERSIONS)'
-endif
-ifdef CONTRACT_VERIFICATION_MAX_LIBRARIES
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CONTRACT_VERIFICATION_MAX_LIBRARIES=$(CONTRACT_VERIFICATION_MAX_LIBRARIES)'
-endif
-ifdef CONTRACT_MAX_STRING_LENGTH_WITHOUT_TRIMMING
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CONTRACT_MAX_STRING_LENGTH_WITHOUT_TRIMMING=$(CONTRACT_MAX_STRING_LENGTH_WITHOUT_TRIMMING)'
-endif
-ifdef CONTRACT_DISABLE_INTERACTION
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'CONTRACT_DISABLE_INTERACTION=$(CONTRACT_DISABLE_INTERACTION)'
-endif
-ifdef EIP_1559_ELASTICITY_MULTIPLIER
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'EIP_1559_ELASTICITY_MULTIPLIER=$(EIP_1559_ELASTICITY_MULTIPLIER)'
-endif
-ifdef API_SENSITIVE_ENDPOINTS_KEY
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_SENSITIVE_ENDPOINTS_KEY=$(API_SENSITIVE_ENDPOINTS_KEY)'
-endif
-ifdef ACCOUNT_VERIFICATION_EMAIL_RESEND_INTERVAL
- BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_VERIFICATION_EMAIL_RESEND_INTERVAL=$(ACCOUNT_VERIFICATION_EMAIL_RESEND_INTERVAL)'
-endif
-
-HAS_BLOCKSCOUT_IMAGE := $(shell docker images | grep -sw "${BS_CONTAINER_IMAGE} ")
-build:
- @echo "==> Checking for blockscout image $(BS_CONTAINER_IMAGE)"
-ifdef HAS_BLOCKSCOUT_IMAGE
- @echo "==> Image exist. Using $(BS_CONTAINER_IMAGE)"
+ifdef BS_BACKEND_STARTED
+ @echo "==> Stopping Blockscout backend container."
+ @docker stop $(BACKEND_CONTAINER_NAME) && docker rm -f $(BACKEND_CONTAINER_NAME)
+ @echo "==> Blockscout backend container stopped."
else
- @echo "==> No image found, trying to build one..."
- @docker build -f ./Dockerfile --build-arg RELEASE_VERSION=$(RELEASE_VERSION) -t $(BS_CONTAINER_IMAGE) ../
+ @echo "==> Blockscout backend container already stopped before."
endif
-
-migrate_only:
- @echo "==> Running migrations"
- @docker run --rm \
- $(BLOCKSCOUT_CONTAINER_PARAMS) \
- $(BS_CONTAINER_IMAGE) /bin/sh -c "echo $$MIX_ENV && ./bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\""
-
-migrate: build postgres
- @$(MAKE) -f $(THIS_FILE) migrate_only
-
-PG_EXIST := $(shell docker ps -a --no-trunc --filter name=^/${PG_CONTAINER_NAME}$ | grep ${PG_CONTAINER_NAME})
-PG_STARTED := $(shell docker ps --no-trunc --filter name=^/${PG_CONTAINER_NAME}$ | grep ${PG_CONTAINER_NAME})
-
-postgres:
-ifdef DATABASE_URL
- @echo "==> DATABASE_URL of external DB provided. There is no need to start a container for DB."
- @$(MAKE) -f $(THIS_FILE) migrate_only
+ifdef BS_STATS_DB_STARTED
+ @echo "==> Stopping Blockscout stats db container."
+ @docker stop $(STATS_DB_CONTAINER_NAME) && docker rm -f $(STATS_DB_CONTAINER_NAME)
+ @echo "==> Blockscout stats db container stopped."
else
-ifdef PG_EXIST
- @echo "==> Checking PostrgeSQL container"
-ifdef PG_STARTED
- @echo "==> PostgreSQL Already started"
- @$(MAKE) -f $(THIS_FILE) migrate_only
+ @echo "==> Blockscout stats db container already stopped before."
+endif
+ifdef BS_STATS_STARTED
+ @echo "==> Stopping Blockscout stats container."
+ @docker stop $(STATS_CONTAINER_NAME) && docker rm -f $(STATS_CONTAINER_NAME)
+ @echo "==> Blockscout stats container stopped."
else
- @echo "==> Starting PostgreSQL container"
- @docker start $(PG_CONTAINER_NAME)
- @$(MAKE) -f $(THIS_FILE) migrate_only
+ @echo "==> Blockscout stats container already stopped before."
endif
+ifdef BS_VISUALIZER_STARTED
+ @echo "==> Stopping Blockscout visualizer container."
+ @docker stop $(VISUALIZER_CONTAINER_NAME) && docker rm -f $(VISUALIZER_CONTAINER_NAME)
+ @echo "==> Blockscout visualizer container stopped."
else
- @echo "==> Creating new PostgreSQL container"
- @docker run -d --name $(PG_CONTAINER_NAME) \
- -e POSTGRES_PASSWORD="" \
- -e POSTGRES_USER="postgres" \
- -e POSTGRES_HOST_AUTH_METHOD="trust" \
- -p 5432:5432 \
- $(PG_CONTAINER_IMAGE)
- @sleep 1
- @$(MAKE) -f $(THIS_FILE) migrate_only
+ @echo "==> Blockscout visualizer container already stopped before."
endif
+ifdef BS_SIG_PROVIDER_STARTED
+ @echo "==> Stopping Blockscout sig-provider container."
+ @docker stop $(SIG_PROVIDER_CONTAINER_NAME) && docker rm -f $(SIG_PROVIDER_CONTAINER_NAME)
+ @echo "==> Blockscout sig-provider container stopped."
+else
+ @echo "==> Blockscout sig-provider container already stopped before."
endif
-
-start: build postgres
- @echo "==> Starting blockscout"
- @docker run --rm --name $(BS_CONTAINER_NAME) \
- $(BLOCKSCOUT_CONTAINER_PARAMS) \
- -p 4000:4000 \
- $(BS_CONTAINER_IMAGE) /bin/sh -c "./bin/blockscout start"
-
-BS_STARTED := $(shell docker ps --no-trunc --filter name=^/${BS_CONTAINER_NAME}$)
-stop:
-ifdef BS_STARTED
- @echo "==> Stopping BlockScout container."
- @docker stop $(BS_CONTAINER_NAME)
- @echo "==> BlockScout container stopped."
+ifdef BS_PROXY_STARTED
+ @echo "==> Stopping Nginx proxy container."
+ @docker stop $(PROXY_CONTAINER_NAME) && docker rm -f $(PROXY_CONTAINER_NAME)
+ @echo "==> Nginx proxy container stopped."
else
- @echo "==> BlockScout container already stopped before."
+ @echo "==> Nginx proxy container already stopped before."
endif
ifdef PG_STARTED
@echo "==> Stopping Postgres container."
@@ -801,15 +110,15 @@ publish-stable: docker-login publish-latest publish-stable-version ## publish th
publish-latest: tag-latest ## publish the `latest` tagged container to hub
@echo 'publish latest to $(DOCKER_REPO)'
- docker push $(BS_CONTAINER_IMAGE):latest
+ docker push $(BACKEND_CONTAINER_IMAGE):latest
publish-version: tag-version ## publish the `{version}` tagged container to hub
@echo 'publish $(TAG) to $(DOCKER_REPO)'
- docker push $(BS_CONTAINER_IMAGE):$(TAG)
+ docker push $(BACKEND_CONTAINER_IMAGE):$(TAG)
publish-stable-version: tag-stable-version ## publish the `{version}` tagged container to hub
@echo 'publish $(STABLE_TAG) to $(DOCKER_REPO)'
- docker push $(BS_CONTAINER_IMAGE):$(STABLE_TAG)
+ docker push $(BACKEND_CONTAINER_IMAGE):$(STABLE_TAG)
# Docker tagging
tag: tag-latest tag-version ## Generate container tags for the `{version}` ans `latest` tags
@@ -817,21 +126,19 @@ tag-stable: tag-latest tag-stable-version ## Generate container tags for the `{v
tag-latest: ## Generate container `latest` tag
@echo 'create latest tag'
- docker tag $(BS_CONTAINER_IMAGE) $(BS_CONTAINER_IMAGE):latest
+ docker tag $(BACKEND_CONTAINER_IMAGE) $(BACKEND_CONTAINER_IMAGE):latest
tag-version: ## Generate container `{version}` tag
@echo 'create tag $(TAG)'
- docker tag $(BS_CONTAINER_IMAGE) $(BS_CONTAINER_IMAGE):$(TAG)
+ docker tag $(BACKEND_CONTAINER_IMAGE) $(BACKEND_CONTAINER_IMAGE):$(TAG)
tag-stable-version: ## Generate container `{version}` tag
@echo 'create tag $(STABLE_TAG)'
- docker tag $(BS_CONTAINER_IMAGE) $(BS_CONTAINER_IMAGE):$(STABLE_TAG)
+ docker tag $(BACKEND_CONTAINER_IMAGE) $(BACKEND_CONTAINER_IMAGE):$(STABLE_TAG)
.PHONY: build \
- migrate \
start \
stop \
- postgres \
run \
docker-login \
release \
@@ -841,4 +148,3 @@ tag-stable-version: ## Generate container `{version}` tag
tag \
tag-latest \
tag-version
-
diff --git a/mix.exs b/mix.exs
index f4ac8ae324c2..ac067de5bd85 100644
--- a/mix.exs
+++ b/mix.exs
@@ -7,7 +7,7 @@ defmodule BlockScout.Mixfile do
[
# app: :block_scout,
# aliases: aliases(config_env()),
- version: "5.1.5",
+ version: "6.0.0",
apps_path: "apps",
deps: deps(),
dialyzer: dialyzer(),
@@ -52,8 +52,8 @@ defmodule BlockScout.Mixfile do
defp dialyzer() do
[
- plt_add_deps: :transitive,
- plt_add_apps: ~w(ex_unit mix)a,
+ plt_add_deps: :app_tree,
+ plt_add_apps: ~w(ex_unit mix wallaby)a,
ignore_warnings: ".dialyzer-ignore",
plt_core_path: "priv/plts",
plt_file: {:no_warn, "priv/plts/dialyzer.plt"}
@@ -94,9 +94,9 @@ defmodule BlockScout.Mixfile do
[
{:prometheus_ex, git: "https://github.com/lanodan/prometheus.ex", branch: "fix/elixir-1.14", override: true},
{:absinthe_plug, git: "https://github.com/blockscout/absinthe_plug.git", tag: "1.5.3", override: true},
- {:tesla, "~> 1.7.0"},
+ {:tesla, "~> 1.8.0"},
# Documentation
- {:ex_doc, "~> 0.29.0", only: :dev, runtime: false},
+ {:ex_doc, "~> 0.31.0", only: :dev, runtime: false},
{:number, "~> 1.0.3"}
]
end
diff --git a/mix.lock b/mix.lock
index acd1b4d9a85d..eff0c9efd4a6 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,115 +1,116 @@
%{
- "absinthe": {:hex, :absinthe, "1.7.1", "aca6f64994f0914628429ddbdfbf24212747b51780dae189dd98909da911757b", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.2.2 or ~> 1.3.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0 or ~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c0c4dbd93881fa3bfbad255608234b104b877c2a901850c1fe8c53b408a72a57"},
+ "absinthe": {:hex, :absinthe, "1.7.6", "0b897365f98d068cfcb4533c0200a8e58825a4aeeae6ec33633ebed6de11773b", [:mix], [{:dataloader, "~> 1.0.0 or ~> 2.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:opentelemetry_process_propagator, "~> 0.2.1", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0 or ~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7626951ca5eec627da960615b51009f3a774765406ff02722b1d818f17e5778"},
"absinthe_phoenix": {:hex, :absinthe_phoenix, "2.0.2", "e607b438db900049b9b3760f8ecd0591017a46122fffed7057bf6989020992b5", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:absinthe_plug, "~> 1.5", [hex: :absinthe_plug, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.5", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "d36918925c380dc7d2ed7d039c9a3b4182ec36723f7417a68745ade5aab22f8d"},
"absinthe_plug": {:git, "https://github.com/blockscout/absinthe_plug.git", "c435d43f316769e1beee1dbe500b623124c96785", [tag: "1.5.3"]},
"absinthe_relay": {:hex, :absinthe_relay, "1.5.2", "cfb8aed70f4e4c7718d3f1c212332d2ea728f17c7fc0f68f1e461f0f5f0c4b9a", [:mix], [{:absinthe, "~> 1.5.0 or ~> 1.6.0 or ~> 1.7.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "0587ee913afa31512e1457a5064ee88427f8fe7bcfbeeecd41c71d9cff0b62b6"},
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"},
"bamboo": {:hex, :bamboo, "2.3.0", "d2392a2cabe91edf488553d3c70638b532e8db7b76b84b0a39e3dfe492ffd6fc", [:mix], [{:hackney, ">= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.4 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "dd0037e68e108fd04d0e8773921512c940e35d981e097b5793543e3b2f9cd3f6"},
- "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"},
- "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"},
+ "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.1.0", "0b110a9a6c619b19a7f73fa3004aa11d6e719a67e672d1633dc36b6b2290a0f7", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2ad2acb5a8bc049e8d5aa267802631912bb80d5f4110a178ae7999e69dca1bf7"},
+ "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"},
"benchee_csv": {:hex, :benchee_csv, "1.0.0", "0b3b9223290bfcb8003552705bec9bcf1a89b4a83b70bd686e45295c264f3d16", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:csv, "~> 2.0", [hex: :csv, repo: "hexpm", optional: false]}], "hexpm", "cdefb804c021dcf7a99199492026584be9b5a21d6644ac0d01c81c5d97c520d5"},
- "briefly": {:git, "https://github.com/CargoSense/briefly.git", "20d1318ec22259923ebd94331c0227895f1faa70", []},
+ "briefly": {:git, "https://github.com/CargoSense/briefly.git", "4836ba322ffb504a102a15cc6e35d928ef97120e", []},
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
"bureaucrat": {:hex, :bureaucrat, "0.2.9", "d98e4d2b9bdbf22e4a45c2113ce8b38b5b63278506c6ff918e3b943a4355d85b", [:mix], [{:inflex, ">= 1.10.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.2.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, ">= 1.0.0", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "111c8dd84382a62e1026ae011d592ceee918553e5203fe8448d9ba6ccbdfff7d"},
"bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"},
- "castore": {:hex, :castore, "1.0.2", "0c6292ecf3e3f20b7c88408f00096337c4bfd99bd46cc2fe63413ddbe45b3573", [:mix], [], "hexpm", "40b2dd2836199203df8500e4a270f10fc006cc95adc8a319e148dc3077391d96"},
+ "castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"},
"cbor": {:hex, :cbor, "1.0.1", "39511158e8ea5a57c1fcb9639aaa7efde67129678fee49ebbda780f6f24959b0", [:mix], [], "hexpm", "5431acbe7a7908f17f6a9cd43311002836a34a8ab01876918d8cfb709cd8b6a2"},
- "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
- "cldr_utils": {:hex, :cldr_utils, "2.23.1", "5c7df90f10b2ffe2519124f3c0cba1bfc0a303d91c34c2e130e86c4f7bf1840c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "42242882f76499fc28e59c80da8b832904cbe68ea50eeb42e65350b0dae7640c"},
+ "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
+ "cldr_utils": {:hex, :cldr_utils, "2.24.2", "364fa30be55d328e704629568d431eb74cd2f085752b27f8025520b566352859", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "3362b838836a9f0fa309de09a7127e36e67310e797d556db92f71b548832c7cf"},
"cloak": {:hex, :cloak, "1.1.2", "7e0006c2b0b98d976d4f559080fabefd81f0e0a50a3c4b621f85ceeb563e80bb", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "940d5ac4fcd51b252930fd112e319ea5ae6ab540b722f3ca60a85666759b9585"},
"cloak_ecto": {:hex, :cloak_ecto, "1.2.0", "e86a3df3bf0dc8980f70406bcb0af2858bac247d55494d40bc58a152590bd402", [:mix], [{:cloak, "~> 1.1.1", [hex: :cloak, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "8bcc677185c813fe64b786618bd6689b1707b35cd95acaae0834557b15a0c62f"},
"coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
- "comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"},
+ "comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"},
"con_cache": {:hex, :con_cache, "1.0.0", "6405e2bd5d5005334af72939432783562a8c35a196c2e63108fe10bb97b366e6", [:mix], [], "hexpm", "4d1f5cb1a67f3c1a468243dc98d10ac83af7f3e33b7e7c15999dc2c9bc0a551e"},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cors_plug": {:hex, :cors_plug, "3.0.3", "7c3ac52b39624bc616db2e937c282f3f623f25f8d550068b6710e58d04a0e330", [:mix], [{:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d"},
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"},
"cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"},
- "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"},
+ "credo": {:hex, :credo, "1.7.1", "6e26bbcc9e22eefbff7e43188e69924e78818e2fe6282487d0703652bc20fd62", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e9871c6095a4c0381c89b6aa98bc6260a8ba6addccf7f6a53da8849c748a58a2"},
"csv": {:hex, :csv, "2.5.0", "c47b5a5221bf2e56d6e8eb79e77884046d7fd516280dc7d9b674251e0ae46246", [:mix], [{:parallel_stream, "~> 1.0.4 or ~> 1.1.0", [hex: :parallel_stream, repo: "hexpm", optional: false]}], "hexpm", "e821f541487045c7591a1963eeb42afff0dfa99bdcdbeb3410795a2f59c77d34"},
- "dataloader": {:hex, :dataloader, "1.0.10", "a42f07641b1a0572e0b21a2a5ae1be11da486a6790f3d0d14512d96ff3e3bbe9", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0 or ~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "54cd70cec09addf4b2ace14cc186a283a149fd4d3ec5475b155951bf33cd963f"},
- "db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"},
+ "dataloader": {:hex, :dataloader, "1.0.11", "49bbfc7dd8a1990423c51000b869b1fecaab9e3ccd6b29eab51616ae8ad0a2f5", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0 or ~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba0b0ec532ec68e9d033d03553561d693129bd7cbd5c649dc7903f07ffba08fe"},
+ "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"},
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"decorator": {:hex, :decorator, "1.4.0", "a57ac32c823ea7e4e67f5af56412d12b33274661bb7640ec7fc882f8d23ac419", [:mix], [], "hexpm", "0a07cedd9083da875c7418dea95b78361197cf2bf3211d743f6f7ce39656597f"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
- "dialyxir": {:hex, :dialyxir, "1.3.0", "fd1672f0922b7648ff9ce7b1b26fcf0ef56dda964a459892ad15f6b4410b5284", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "00b2a4bcd6aa8db9dcb0b38c1225b7277dca9bc370b6438715667071a304696f"},
- "digital_token": {:hex, :digital_token, "0.4.0", "2ad6894d4a40be8b2890aad286ecd5745fa473fa5699d80361a8c94428edcd1f", [:mix], [{:cldr_utils, "~> 2.17", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "a178edf61d1fee5bb3c34e14b0f4ee21809ee87cade8738f87337e59e5e66e26"},
- "earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"},
- "ecto": {:hex, :ecto, "3.10.1", "c6757101880e90acc6125b095853176a02da8f1afe056f91f1f90b80c9389822", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d2ac4255f1601bdf7ac74c0ed971102c6829dc158719b94bd30041bbad77f87a"},
- "ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"},
- "elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"},
+ "dialyxir": {:hex, :dialyxir, "1.4.2", "764a6e8e7a354f0ba95d58418178d486065ead1f69ad89782817c296d0d746a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "516603d8067b2fd585319e4b13d3674ad4f314a5902ba8130cd97dc902ce6bbd"},
+ "digital_token": {:hex, :digital_token, "0.6.0", "13e6de581f0b1f6c686f7c7d12ab11a84a7b22fa79adeb4b50eec1a2d278d258", [:mix], [{:cldr_utils, "~> 2.17", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "2455d626e7c61a128b02a4a8caddb092548c3eb613ac6f6a85e4cbb6caddc4d1"},
+ "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
+ "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"},
+ "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"},
+ "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
- "ex_abi": {:hex, :ex_abi, "0.6.0", "8cf1fef9490dea0834bc201d399635e72178df05dea87b1c933478762dede142", [:mix], [{:ex_keccak, "~> 0.7.1", [hex: :ex_keccak, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b03e5fe07371db3ceceb2d536cc32658dcba47b79952469e3e71d7690495e8d8"},
- "ex_cldr": {:hex, :ex_cldr, "2.37.1", "6091fa719a7a96f9abee7aba186e63a906d504d08039cc8f0c683a0e71ee1bd7", [:mix], [{:cldr_utils, "~> 2.21", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "5d60c3288454bc966e404ea4f59531f7dbb570d7e927dce62f0ab8466713bf78"},
- "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.15.0", "aadd34e91cfac7ef6b03fe8f47f8c6fa8c5daf3f89b5d9fee64ec545ded839cf", [:mix], [{:ex_cldr, "~> 2.34", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "0521316396c66877a2d636219767560bb2397c583341fcb154ecf9f3000e6ff8"},
- "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.10.0", "4d4c9877da2d0417fd832907d69974e8328969f75fafc79b05ccf85f549f6281", [:mix], [{:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "adc040cde7b97f7fd7c0b35dd69ddb6fcf607303ae6355bb1851deae1f8b0652"},
- "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.31.1", "ad3e8a33e8cb8d048173f2c89cf6fcec9f1694d99f890a75bc745894c3868d5b", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.37", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, ">= 2.14.2", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "a94c40f4bf60f0e69c34977f33caeda483677232699ab0a6a98025ea011fabcf"},
- "ex_cldr_units": {:hex, :ex_cldr_units, "3.16.0", "90e4f89a12dc94de561a435f10cb0c2059a1f29942e41ba3352cd37cb673769c", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr_lists, "~> 2.10", [hex: :ex_cldr_lists, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.31", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "0546957cbb2d92a675fba91c6819a0a42b028f3193d1c4c7ad27d241e492520b"},
- "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"},
- "ex_json_schema": {:hex, :ex_json_schema, "0.9.2", "c9a42e04e70cd70eb11a8903a22e8ec344df16edef4cb8e6ec84ed0caffc9f0f", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "4854329cb352b6c01c4c4b8dbfb3be14dc5bea19ea13e0eafade4ff22ba55224"},
- "ex_keccak": {:hex, :ex_keccak, "0.7.1", "0169f4b0c5073c5df61581d6282b12f1a1b764dcfcda4eeb1c819b5194c9ced0", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.6.1", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "c18c19f66b6545b4b46b0c71c0cc0079de84e30b26365a92961e91697e8724ed"},
+ "ex_abi": {:hex, :ex_abi, "0.6.4", "f722a38298f176dab511cf94627b2815282669255bc2eb834674f23ca71f5cfb", [:mix], [{:ex_keccak, "~> 0.7.3", [hex: :ex_keccak, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "07eaf39b70dd3beac1286c10368d27091a9a64844830eb26a38f1c8d8b19dfbb"},
+ "ex_cldr": {:hex, :ex_cldr, "2.37.5", "9da6d97334035b961d2c2de167dc6af8cd3e09859301a5b8f49f90bd8b034593", [:mix], [{:cldr_utils, "~> 2.21", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "74ad5ddff791112ce4156382e171a5f5d3766af9d5c4675e0571f081fe136479"},
+ "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.15.1", "e92ba17c41e7405b7784e0e65f406b5f17cfe313e0e70de9befd653e12854822", [:mix], [{:ex_cldr, "~> 2.34", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "31df8bd37688340f8819bdd770eb17d659652078d34db632b85d4a32864d6a25"},
+ "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.10.2", "c8dbb3324ca35cea3679a96f4c774cdf4bdd425786a44c4f52aacb57a0cee446", [:mix], [{:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "0cc2124eccffa5438045c2504dd3365490b64065131f58ecc27f344db1edb4b8"},
+ "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.32.3", "b631ff94c982ec518e46bf4736000a30a33d6b58facc085d5f240305f512ad4a", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.37", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, ">= 2.14.2", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "7b626ff1e59a0ec9c3c5db5ce9ca91a6995e2ab56426b71f3cbf67181ea225f5"},
+ "ex_cldr_units": {:hex, :ex_cldr_units, "3.16.4", "fee054e9ebed40ef05cbb405cb0c7e7c9fda201f8f03ec0d1e54e879af413246", [:mix], [{:cldr_utils, "~> 2.24", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr_lists, "~> 2.10", [hex: :ex_cldr_lists, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.31", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "7c15c6357dd555a5bc6c72fdeb243e4706a04065753dbd2f40150f062ca996c7"},
+ "ex_doc": {:hex, :ex_doc, "0.31.0", "06eb1dfd787445d9cab9a45088405593dd3bb7fe99e097eaa71f37ba80c7a676", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5350cafa6b7f77bdd107aa2199fe277acf29d739aba5aee7e865fc680c62a110"},
+ "ex_json_schema": {:hex, :ex_json_schema, "0.10.2", "7c4b8c1481fdeb1741e2ce66223976edfb9bccebc8014f6aec35d4efe964fb71", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "37f43be60f8407659d4d0155a7e45e7f406dab1f827051d3d35858a709baf6a6"},
+ "ex_keccak": {:hex, :ex_keccak, "0.7.3", "33298f97159f6b0acd28f6e96ce5ea975a0f4a19f85fe615b4f4579b88b24d06", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.6.1", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "4c5e6d9d5f77b64ab48769a0166a9814180d40ced68ed74ce60a5174ab55b3fc"},
"ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
"ex_rlp": {:hex, :ex_rlp, "0.6.0", "985391d2356a7cb8712a4a9a2deb93f19f2fbca0323f5c1203fcaf64d077e31e", [:mix], [], "hexpm", "7135db93b861d9e76821039b60b00a6a22d2c4e751bf8c444bffe7a042f1abaf"},
+ "ex_secp256k1": {:hex, :ex_secp256k1, "0.7.2", "33398c172813b90fab9ab75c12b98d16cfab472c6dcbde832b13c45ce1c01947", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.6", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "f3b1bf56e6992e28b9d86e3bf741a4aca3e641052eb47d13ae4f5f4d4944bdaf"},
"ex_utils": {:hex, :ex_utils, "0.1.7", "2c133e0bcdc49a858cf8dacf893308ebc05bc5fba501dc3d2935e65365ec0bf3", [:mix], [], "hexpm", "66d4fe75285948f2d1e69c2a5ddd651c398c813574f8d36a9eef11dc20356ef6"},
"exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], [], "hexpm", "1222419f706e01bfa1095aec9acf6421367dcfab798a6f67c54cf784733cd6b5"},
"exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm", "32e95820a97cffea67830e91514a2ad53b888850442d6d395f53a1ac60c82e07"},
- "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"},
- "exvcr": {:hex, :exvcr, "0.14.1", "d9aacec631ed379e366bce5b3c68f6ec5b92b89142f9379425854e80a28c1107", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:finch, "~> 0.16", [hex: :finch, repo: "hexpm", optional: true]}, {:httpoison, "~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "5f1e0854fbad0c4bb64ae8cdd289eeace90b8a0a209788c2f840d70f86a2d63c"},
+ "expo": {:hex, :expo, "0.5.1", "249e826a897cac48f591deba863b26c16682b43711dd15ee86b92f25eafd96d9", [:mix], [], "hexpm", "68a4233b0658a3d12ee00d27d37d856b1ba48607e7ce20fd376958d0ba6ce92b"},
+ "exvcr": {:hex, :exvcr, "0.15.0", "432a4f4b94494f996c96dd2b9b9d3306b70db269ddbdeb9e324a4371f62ce32d", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:finch, "~> 0.16", [hex: :finch, repo: "hexpm", optional: true]}, {:httpoison, "~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "8b7e451f5fd37d1dc1252d08e55291fcb80b55b00cfd84ea41bf64be23cb142c"},
"file_info": {:hex, :file_info, "0.0.4", "2e0e77f211e833f38ead22cb29ce53761d457d80b3ffe0ffe0eb93880b0963b2", [:mix], [{:mimetype_parser, "~> 0.1.2", [hex: :mimetype_parser, repo: "hexpm", optional: false]}], "hexpm", "50e7ad01c2c8b9339010675fe4dc4a113b8d6ca7eddce24d1d74fd0e762781a5"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
- "floki": {:hex, :floki, "0.34.2", "5fad07ef153b3b8ec110b6b155ec3780c4b2c4906297d0b4be1a7162d04a7e02", [:mix], [], "hexpm", "26b9d50f0f01796bc6be611ca815c5e0de034d2128e39cc9702eee6b66a4d1c8"},
+ "floki": {:hex, :floki, "0.35.2", "87f8c75ed8654b9635b311774308b2760b47e9a579dabf2e4d5f1e1d42c39e0b", [:mix], [], "hexpm", "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9"},
"flow": {:hex, :flow, "1.2.4", "1dd58918287eb286656008777cb32714b5123d3855956f29aa141ebae456922d", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm", "874adde96368e71870f3510b91e35bc31652291858c86c0e75359cbdd35eb211"},
"gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"},
- "gettext": {:hex, :gettext, "0.22.1", "e7942988383c3d9eed4bdc22fc63e712b655ae94a672a27e4900e3d4a2c43581", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "ad105b8dab668ee3f90c0d3d94ba75e9aead27a62495c101d94f2657a190ac5d"},
- "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
+ "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"},
+ "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~>2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
"hammer": {:hex, :hammer, "6.1.0", "f263e3c3e9946bd410ea0336b2abe0cb6260af4afb3a221e1027540706e76c55", [:make, :mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "b47e415a562a6d072392deabcd58090d8a41182cf9044cdd6b0d0faaaf68ba57"},
"hammer_backend_redis": {:hex, :hammer_backend_redis, "6.1.2", "eb296bb4924928e24135308b2afc189201fd09411c870c6bbadea444a49b2f2c", [:mix], [{:hammer, "~> 6.0", [hex: :hammer, repo: "hexpm", optional: false]}, {:redix, "~> 1.1", [hex: :redix, repo: "hexpm", optional: false]}], "hexpm", "217ea066278910543a5e9b577d5bf2425419446b94fe76bdd9f255f39feec9fa"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
- "httpoison": {:hex, :httpoison, "2.1.0", "655fd9a7b0b95ee3e9a3b535cf7ac8e08ef5229bab187fa86ac4208b122d934b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "fc455cb4306b43827def4f57299b2d5ac8ac331cb23f517e734a4b78210a160c"},
+ "httpoison": {:hex, :httpoison, "2.2.1", "87b7ed6d95db0389f7df02779644171d7319d319178f6680438167d7b69b1f3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "51364e6d2f429d80e14fe4b5f8e39719cacd03eb3f9a9286e61e216feac2d2df"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"},
- "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
+ "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm", "fc3499fed7a726995aa659143a248534adc754ebd16ccd437cd93b649a95091f"},
"junit_formatter": {:hex, :junit_formatter, "3.3.1", "c729befb848f1b9571f317d2fefa648e9d4869befc4b2980daca7c1edc468e40", [:mix], [], "hexpm", "761fc5be4b4c15d8ba91a6dafde0b2c2ae6db9da7b8832a55b5a1deb524da72b"},
- "libsecp256k1": {:hex, :libsecp256k1, "0.1.10", "d27495e2b9851c7765129b76c53b60f5e275bd6ff68292c50536bf6b8d091a4d", [:make, :mix], [{:mix_erlang_tasks, "0.1.0", [hex: :mix_erlang_tasks, repo: "hexpm", optional: false]}], "hexpm", "09ea06239938571124f7f5a27bc9ac45dfb1cfc2df40d46ee9b59c3d51366652"},
"logger_file_backend": {:hex, :logger_file_backend, "0.0.13", "df07b14970e9ac1f57362985d76e6f24e3e1ab05c248055b7d223976881977c2", [:mix], [], "hexpm", "71a453a7e6e899ae4549fb147b1c6621f4233f8f48f58ca10a64ec67b6c50018"},
- "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
+ "logger_json": {:hex, :logger_json, "5.1.2", "7dde5f6dff814aba033f045a3af9408f5459bac72357dc533276b47045371ecf", [:mix], [{:ecto, "~> 2.1 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "ed42047e5c57a60d0fa1450aef36bc016d0f9a5e6c0807ebb0c03d8895fb6ebc"},
+ "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
- "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
+ "makeup_erlang": {:hex, :makeup_erlang, "0.1.3", "d684f4bac8690e70b06eb52dad65d26de2eefa44cd19d64a8095e1417df7c8fd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9"},
"math": {:hex, :math, "0.7.0", "12af548c3892abf939a2e242216c3e7cbfb65b9b2fe0d872d05c6fb609f8127b", [:mix], [], "hexpm", "7987af97a0c6b58ad9db43eb5252a49fc1dfe1f6d98f17da9282e297f594ebc2"},
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
"memento": {:hex, :memento, "0.3.2", "38cfc8ff9bcb1adff7cbd0f3b78a762636b86dff764729d1c82d0464c539bdd0", [:mix], [], "hexpm", "25cf691a98a0cb70262f4a7543c04bab24648cb2041d937eb64154a8d6f8012b"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
- "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
+ "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"mimetype_parser": {:hex, :mimetype_parser, "0.1.3", "628ac9fe56aa7edcedb534d68397dd66674ab82493c8ebe39acb9a19b666099d", [:mix], [], "hexpm", "7d8f80c567807ce78cd93c938e7f4b0a20b1aaaaab914bf286f68457d9f7a852"},
"mix_erlang_tasks": {:hex, :mix_erlang_tasks, "0.1.0", "36819fec60b80689eb1380938675af215565a89320a9e29c72c70d97512e4649", [:mix], [], "hexpm", "95d2839c422c482a70c08a8702da8242f86b773f8ab6e8602a4eb72da8da04ed"},
- "mock": {:hex, :mock, "0.3.7", "75b3bbf1466d7e486ea2052a73c6e062c6256fb429d6797999ab02fa32f29e03", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4da49a4609e41fd99b7836945c26f373623ea968cfb6282742bcb94440cf7e5c"},
- "mox": {:hex, :mox, "1.0.2", "dc2057289ac478b35760ba74165b4b3f402f68803dd5aecd3bfd19c183815d64", [:mix], [], "hexpm", "f9864921b3aaf763c8741b5b8e6f908f44566f1e427b2630e89e9a73b981fef2"},
- "msgpax": {:hex, :msgpax, "2.3.1", "28e17c4abb4c57da742e75de62abd9d01c76f1da0b103334de3fb1199610b3d9", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "17c8bf2fc2327b74e4bc6633dd520ffa10ea07b0a2f8ab1932db99044e116df5"},
+ "mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"},
+ "mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"},
+ "msgpax": {:hex, :msgpax, "2.4.0", "4647575c87cb0c43b93266438242c21f71f196cafa268f45f91498541148c15d", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "ca933891b0e7075701a17507c61642bf6e0407bb244040d5d0a58597a06369d2"},
"nimble_csv": {:hex, :nimble_csv, "1.2.0", "4e26385d260c61eba9d4412c71cea34421f296d5353f914afe3f2e71cce97722", [:mix], [], "hexpm", "d0628117fcc2148178b034044c55359b26966c6eaa8e2ce15777be3bbc91b12a"},
"nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"},
- "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"},
- "number": {:hex, :number, "1.0.3", "932c8a2d478a181c624138958ca88a78070332191b8061717270d939778c9857", [:mix], [{:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "dd397bbc096b2ca965a6a430126cc9cf7b9ef7421130def69bcf572232ca0f18"},
+ "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
+ "number": {:hex, :number, "1.0.4", "3e6e6032a3c1d4c3760e77a42c580a57a15545dd993af380809da30fe51a032c", [:mix], [{:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "16f7516584ef2be812af4f33f2eaf3f9b9f6ed8892f45853eb93113f83721e42"},
"numbers": {:hex, :numbers, "5.2.4", "f123d5bb7f6acc366f8f445e10a32bd403c8469bdbce8ce049e1f0972b607080", [:mix], [{:coerce, "~> 1.0", [hex: :coerce, repo: "hexpm", optional: false]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "eeccf5c61d5f4922198395bf87a465b6f980b8b862dd22d28198c5e6fab38582"},
"oauth2": {:hex, :oauth2, "2.0.1", "70729503e05378697b958919bb2d65b002ba6b28c8112328063648a9348aaa3f", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "c64e20d4d105bcdbcbe03170fb530d0eddc3a3e6b135a87528a22c8aecf74c52"},
"optimal": {:hex, :optimal, "0.3.6", "46bbf52fbbbd238cda81e02560caa84f93a53c75620f1fe19e81e4ae7b07d1dd", [:mix], [], "hexpm", "1a06ea6a653120226b35b283a1cd10039550f2c566edcdec22b29316d73640fd"},
"parallel_stream": {:hex, :parallel_stream, "1.1.0", "f52f73eb344bc22de335992377413138405796e0d0ad99d995d9977ac29f1ca9", [:mix], [], "hexpm", "684fd19191aedfaf387bbabbeb8ff3c752f0220c8112eb907d797f4592d6e871"},
- "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
- "phoenix": {:hex, :phoenix, "1.5.13", "d4e0805ec0973bed80d67302631130fb47d75b1a0b7335a0b23c4432b6ce55ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1a7c4f1900e6e60bb60ae6680e48418e3f7c360d58bcb9f812487b6d0d281a0f"},
- "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.1", "fe7a02387a7d26002a46b97e9879591efee7ebffe5f5e114fd196632e6e4a08d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ddccf8b4966180afe7630b105edb3402b1ca485e7468109540d262e842048ba4"},
+ "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
+ "phoenix": {:hex, :phoenix, "1.5.14", "2d5db884be496eefa5157505ec0134e66187cb416c072272420c5509d67bf808", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "207f1aa5520320cbb7940d7ff2dde2342162cf513875848f88249ea0ba02fef7"},
+ "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.3", "86e9878f833829c3f66da03d75254c155d91d72a201eb56ae83482328dc7ca93", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d36c401206f3011fefd63d04e8ef626ec8791975d9d107f9a0817d426f61ac07"},
"phoenix_html": {:hex, :phoenix_html, "3.0.4", "232d41884fe6a9c42d09f48397c175cd6f0d443aaa34c7424da47604201df2e1", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "ce17fd3cf815b2ed874114073e743507704b1f5288bb03c304a77458485efc8b"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"},
- "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
- "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"},
+ "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
+ "plug": {:hex, :plug, "1.15.2", "94cf1fa375526f30ff8770837cb804798e0045fd97185f0bb9e5fcd858c792a3", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02731fa0c2dcb03d8d21a1d941bdbbe99c2946c0db098eee31008e04c6283615"},
"plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
"plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
"poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
- "postgrex": {:hex, :postgrex, "0.17.1", "01c29fd1205940ee55f7addb8f1dc25618ca63a8817e56fac4f6846fc2cddcbe", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "14b057b488e73be2beee508fb1955d8db90d6485c6466428fe9ccf1d6692a555"},
- "prometheus": {:hex, :prometheus, "4.10.0", "792adbf0130ff61b5fa8826f013772af24b6e57b984445c8d602c8a0355704a1", [:mix, :rebar3], [{:quantile_estimator, "~> 0.2.1", [hex: :quantile_estimator, repo: "hexpm", optional: false]}], "hexpm", "2a99bb6dce85e238c7236fde6b0064f9834dc420ddbd962aac4ea2a3c3d59384"},
+ "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"},
+ "prometheus": {:hex, :prometheus, "4.11.0", "b95f8de8530f541bd95951e18e355a840003672e5eda4788c5fa6183406ba29a", [:mix, :rebar3], [{:quantile_estimator, "~> 0.2.1", [hex: :quantile_estimator, repo: "hexpm", optional: false]}], "hexpm", "719862351aabf4df7079b05dc085d2bbcbe3ac0ac3009e956671b1d5ab88247d"},
"prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"},
"prometheus_ex": {:git, "https://github.com/lanodan/prometheus.ex", "31f7fbe4b71b79ba27efc2a5085746c4011ceb8f", [branch: "fix/elixir-1.14"]},
"prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "c4d1404ac4e9d3d963da601db2a7d8ea31194f0017057fabf0cfb9bf5a6c8c75"},
@@ -120,25 +121,25 @@
"que": {:hex, :que, "0.10.1", "788ed0ec92ed69bdf9cfb29bf41a94ca6355b8d44959bd0669cf706e557ac891", [:mix], [{:ex_utils, "~> 0.1.6", [hex: :ex_utils, repo: "hexpm", optional: false]}, {:memento, "~> 0.3.0", [hex: :memento, repo: "hexpm", optional: false]}], "hexpm", "a737b365253e75dbd24b2d51acc1d851049e87baae08cd0c94e2bc5cd65088d5"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"ratio": {:hex, :ratio, "2.4.2", "c8518f3536d49b1b00d88dd20d49f8b11abb7819638093314a6348139f14f9f9", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:numbers, "~> 5.2.0", [hex: :numbers, repo: "hexpm", optional: false]}], "hexpm", "441ef6f73172a3503de65ccf1769030997b0d533b1039422f1e5e0e0b4cbf89e"},
- "redix": {:hex, :redix, "1.2.3", "3036e7c6080c42e1bbaa9168d1e28e367b01e8960a640a899b8ef8067273cb5e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:nimble_options, "~> 0.5.0 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "14e2bca8a03fad297a78a3d201032df260ee5f0e0ef9c173c0f9ca5b3e0331b7"},
+ "redix": {:hex, :redix, "1.3.0", "f4121163ff9d73bf72157539ff23b13e38422284520bb58c05e014b19d6f0577", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:nimble_options, "~> 0.5.0 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "60d483d320c77329c8cbd3df73007e51b23f3fae75b7693bc31120d83ab26131"},
"remote_ip": {:hex, :remote_ip, "1.1.0", "cb308841595d15df3f9073b7c39243a1dd6ca56e5020295cb012c76fbec50f2d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "616ffdf66aaad6a72fc546dabf42eed87e2a99e97b09cbd92b10cc180d02ed74"},
- "rustler_precompiled": {:hex, :rustler_precompiled, "0.6.1", "160b545bce8bf9a3f1b436b2c10f53574036a0db628e40f393328cbbe593602f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "0dd269fa261c4e3df290b12031c575fff07a542749f7b0e8b744d72d66c43600"},
- "sobelow": {:hex, :sobelow, "0.12.2", "45f4d500e09f95fdb5a7b94c2838d6b26625828751d9f1127174055a78542cf5", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "2f0b617dce551db651145662b84c8da4f158e7abe049a76daaaae2282df01c5d"},
+ "rustler_precompiled": {:hex, :rustler_precompiled, "0.6.3", "f838d94bc35e1844973ee7266127b156fdc962e9e8b7ff666c8fb4fed7964d23", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "e18ecca3669a7454b3a2be75ae6c3ef01d550bc9a8cf5fbddcfff843b881d7c6"},
+ "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"},
"spandex": {:hex, :spandex, "3.2.0", "f8cd40146ea988c87f3c14054150c9a47ba17e53cd4515c00e1f93c29c45404d", [:mix], [{:decorator, "~> 1.2", [hex: :decorator, repo: "hexpm", optional: true]}, {:optimal, "~> 0.3.3", [hex: :optimal, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d0a7d5aef4c5af9cf5467f2003e8a5d8d2bdae3823a6cc95d776b9a2251d4d03"},
- "spandex_datadog": {:hex, :spandex_datadog, "1.3.0", "cabe82980f55612a8befa6c12904b1a429bf17faf7271a94b9aae278af6362cf", [:mix], [{:msgpax, "~> 2.2.1 or ~> 2.3", [hex: :msgpax, repo: "hexpm", optional: false]}, {:spandex, "~> 3.0", [hex: :spandex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c826e4e29d1612e866b2c7bae2df3beeee84fc57351968c2772672afe59b789c"},
+ "spandex_datadog": {:hex, :spandex_datadog, "1.4.0", "0594b9655b0af00ab9137122616bc0208b68ceec01e9916ab13d6fbb33dcce35", [:mix], [{:msgpax, "~> 2.2.1 or ~> 2.3", [hex: :msgpax, repo: "hexpm", optional: false]}, {:spandex, "~> 3.2", [hex: :spandex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "360f8e1b4db238c1749c4872b1697b096429927fa42b8858d0bb782067380123"},
"spandex_ecto": {:hex, :spandex_ecto, "0.7.0", "259ad2feb7c834e774ec623f99c0fbacca8d60a73be212f92b75e37f853c81be", [:mix], [{:spandex, "~> 2.2 or ~> 3.0", [hex: :spandex, repo: "hexpm", optional: false]}], "hexpm", "c64784be79d95538013b7c60828830411c5c7aff1f4e8d66dfe564b3c83b500e"},
"spandex_phoenix": {:hex, :spandex_phoenix, "1.1.0", "9cff829d05258dd49a227c56711b19b69a8fd5d4873d8e9a92a4f4097e7322ab", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.3", [hex: :plug, repo: "hexpm", optional: false]}, {:spandex, "~> 2.2 or ~> 3.0", [hex: :spandex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "265fe05c1736485fbb75d66ef7576682ebf6428c391dd54d22217f612fd4ddad"},
- "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
+ "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
"telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"},
- "tesla": {:hex, :tesla, "1.7.0", "a62dda2f80d4f8a925eb7b8c5b78c461e0eb996672719fe1a63b26321a5f8b4e", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2e64f01ebfdb026209b47bc651a0e65203fcff4ae79c11efb73c4852b00dc313"},
+ "tesla": {:hex, :tesla, "1.8.0", "d511a4f5c5e42538d97eef7c40ec4f3e44effdc5068206f42ed859e09e51d1fd", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "10501f360cd926a309501287470372af1a6e1cbed0f43949203a4c13300bc79f"},
"timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
"toml": {:hex, :toml, "0.6.2", "38f445df384a17e5d382befe30e3489112a48d3ba4c459e543f748c2f25dd4d1", [:mix], [], "hexpm", "d013e45126d74c0c26a38d31f5e8e9b83ea19fc752470feb9a86071ca5a672fa"},
"tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"},
- "ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"},
+ "ueberauth": {:hex, :ueberauth, "0.10.7", "5a31cbe11e7ce5c7484d745dc9e1f11948e89662f8510d03c616de03df581ebd", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "0bccf73e2ffd6337971340832947ba232877aa8122dba4c95be9f729c8987377"},
"ueberauth_auth0": {:hex, :ueberauth_auth0, "2.1.0", "0632d5844049fa2f26823f15e1120aa32f27df6f27ce515a4b04641736594bf4", [:mix], [{:oauth2, "~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "8d3b30fa27c95c9e82c30c4afb016251405706d2e9627e603c3c9787fd1314fc"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
- "wallaby": {:hex, :wallaby, "0.30.3", "9213ebf6e22e34544ede60e435bdbf807b67119ba4548f7b9bdbbd53a359767f", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.2.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "40844afbf3bf6933f21406bdba2c59042ea0983b7a2533a51f46d372d79bc400"},
+ "wallaby": {:hex, :wallaby, "0.30.6", "7dc4c1213f3b52c4152581d126632bc7e06892336d3a0f582853efeeabd45a71", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.2.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "50950c1d968549b54c20e16175c68c7fc0824138e2bb93feb11ef6add8eb23d4"},
"web_driver_client": {:hex, :web_driver_client, "0.2.0", "63b76cd9eb3b0716ec5467a0f8bead73d3d9612e63f7560d21357f03ad86e31a", [:mix], [{:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.3", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "83cc6092bc3e74926d1c8455f0ce927d5d1d36707b74d9a65e38c084aab0350f"},
"websocket_client": {:git, "https://github.com/blockscout/websocket_client.git", "0b4ecc5b1fb8a0bd1c8352728da787c20add53aa", [branch: "master"]},
}
diff --git a/rel/config.exs b/rel/config.exs
index d5bfc0099af6..b7c3f38132d3 100644
--- a/rel/config.exs
+++ b/rel/config.exs
@@ -71,7 +71,7 @@ end
# will be used by default
release :blockscout do
- set version: "5.1.5-beta"
+ set version: "6.0.0-beta"
set applications: [
:runtime_tools,
block_scout_web: :permanent,